您的位置:首页 > 编程语言 > C语言/C++

孙鑫老师VC++教学视频学习笔记

2007-03-10 12:37 561 查看
<< 一:掌握C++>>

1:struct 或者 class的定义完成后面一定不要忘记分号!
2: 可以这么写:
cout<<pt.x<<endl<<pt.y<<endl;
不一定非写成:
cout<<pt.x<<endl;
cout<<pt.y<<endl;
前者更简洁!
3: C语言当中struct内部是不能有函数的,而 C++ 中就可以有.
4: C++中的struct内也可以显式使用private,protected等权限修饰符,所以在C++中,struct与class除了默认的访问权限不同,一个为public而另一个为private以外,是可以通用的.通用的意义在于,像下面的代码是可以正确编译,运行的:
struct test
{
public:
test()
{
cout<<"aaa"<<endl;
}
~test()
{
cout<<"bbb"<<endl;
}
};
test a;
void main()
{
cout<<"test"<<endl;
return;
}

也就是说,结构也可以有构造函数,析构函数,其调用机制跟类是一样的.
5: 在一个class中,未初始化的数据成员的值是随机的.没有默认值一说.
6: 下面代码有什么错误吗?
class test
{
test()
{
cout<<"aaa"<<endl;
}
~test()
{
cout<<"bbb"<<endl;
}
};

当然有了--构造函数与析构函数都必须是public的,而默认的类成员函数是private的.
7: 析构函数绝不能有参数,返回值,且一个类只能有一个析构函数.
8: 看下面一段代码:
#include <iostream.h>
class test
{
public:
test()
{
cout<<"aaa"<<endl;
}
~test()
{
cout<<"bbb"<<endl;
}
};
test a;
void main()
{
cout<<"test"<<endl;
return;
}

在VC6.0中的输出是:
aaa
test
bbb

再看下面:

#include <iostream>
using namespace std;

class test
{
public:
test()
{
cout<<"aaa"<<endl;
}
~test()
{
cout<<"bbb"<<endl;
}
};
test a;
void main()
{
cout<<"test"<<endl;
return;
}

VC6.0中的输出的结果是:
aaa
test
也就是说以上的两种不同的头文件包含方式,是有差别的,具体差别还不知道!

9:当C++编译器在编译的时候,如果发现一个类中的某个函数是虚函数,也就是说它前面有virtual修饰符,C++就会采用一种"迟绑定"(late binding)的技术,在运行的时候,依据对象的类型来确认调用的是哪一个函数,这种能力就叫做C++的多态.

10: 引用必须在声明时初始化!引用一旦初始化,它就不能再引用到别的同类型变量上!

<< 二: MFC框架 >>

1:_tWinMain实际上是一个宏,其等于WinMain!

2:_tWinMain()的定义位于文件appmodul.cpp中,而CWinapp的定义位于appcore.cpp中,AfxWinMain()在winmain.cpp当中.而负责注册窗口类的函数AfxEndDeferRegisterClass()位于wincore.cpp当中.至于窗口类的定义,在MFC 中,微软为我们定义了好几个默认的窗口类.

3:所谓CWnd对象,只是在CWnd类中封装了创建窗口的过程然后保存了生成的窗口句柄而已,然后通过构造一个它的对象来启动那些过程,并不是说CWnd对象与窗口有什么固定的关系.又因为CMainFrame与CView都是从CWnd派生而来的,所以CMainFrame与CView对象之于窗口的关系也差不多.也就是说,一个窗口关闭了,并不代表那个对象一定也销毁了.还可以从那个对象出发,再调用一遍其中的函数生成窗口,但是若是对象都销毁了,则窗口一定不会存在!CMainFrame对应窗口的标题栏及菜单栏为非客户区,工具栏及以下为客户区.而CView对象对应窗口的客户区为工具栏以下区域.

4:具体追踪过程见TrackMfc 工程!

5:为了修改程序的背景色,我在View类的PreCreateWindow()中新定义了一个窗口类如下:
WNDCLASS wndcls;
wndcls.cbClsExtra=0;
wndcls.cbWndExtra=0;
wndcls.hbrBackground=(HBRUSH)::CreateSolidBrush(RGB(208,221,238));
wndcls.hCursor=::LoadCursor(NULL, IDC_ARROW);
wndcls.hIcon=LoadIcon(::AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_MYICON));
wndcls.hInstance=AfxGetInstanceHandle();
wndcls.lpfnWndProc=(WNDPROC)::DefWindowProc;
wndcls.lpszClassName="wndcls";
wndcls.lpszMenuName=NULL;
wndcls.style=CS_HREDRAW | CS_VREDRAW;
cs.lpszClass="wndcls";
却发现一启动程序就弹出一个消息框说"建立空文档失败!".几番查找,才发现还要将自定义的窗口类进行注册才可以,在最后一句前面加上一句::RegisterClass(&wndcls);问题就解决了,背景色也变了,可是窗口图标还是没有变成我自画的IDI_MYICON,怎么回事呢?原来改变图标要在MainFrame类的PreCreateWindow()中进行,可是只改变个图标定义一个窗口类太过麻烦了,可以用下面的方法:
cs.lpszClass=AfxRegisterWndClass(NULL,NULL,NULL,AfxGetApp()->LoadIcon(IDI_MYICON));
使用了一个AfxRegisterWndClass()的函数.当然在View类中做一个大大的窗口类也是相当麻烦的,所以也可以使用AfxRegisterWndClass()函数!
只用一句:
cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,::LoadCursor(NULL, IDC_CROSS,(HBRUSH)::CreateSolidBrush(RGB(208,221,238)),0);即可!
做完上面这些,我通过import重新载入了一个ICON,在ResourceView窗口中直接删除了原本自己画的IDI_MYICON,然后将新载入的ICON改ID 为IDI_MYICON,然后重新编译,却得不到想要的效果,然后我在Build菜单下点击了Rebuild All,编译完成后新图标才显示出来,具体原因估计跟预编译有关!

6:还有就是即使如上所示改变了窗口左上角的那个图标,可应用程序自身的图标却还是那个MFC标准图标,这时,你只需将你想要的图标放到工程的res文件夹下,交将之改名为标准MFC图标的名称,再做个Rebuild All就可以将之更改!之于上面的窗口类定义中菜单怎么可以定义为NULL,是因为由MFC产生的代码,菜单的创建是在App类的InitInstance()中在构造单文档模板的时候将菜单的资源标识传递过来,并进行相关的创建工作的!所以上面传递NULL并不会影响菜单的创建的. (相关更多知识见第八课的笔记)!!!!!!!!!!

<< 三: GDI编程 >>
1:如果你想画一个虚线线条,也就是说线形为PS_DASH,那么线宽必须是1才行.具有同样要求的还有PS_DOT...

2:如何创建一个透明画刷
CBrush * pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
这就可以了,然后选入DC使用即可!

3:关于画点,我们一般用SetPixel()函数,可是你会发现不管你把画笔的线宽设为几,画出来的点都是一个像素大小,但通常我们希望我们指定4的大小时可以画一个比原先大小大相应程度的点,这时,我想到的办法就是画点部分用画圆函数来实现,只要把填充画刷设置合理即可.不知还有没别的办法.

<< 四:文本编程 >>
1:创建普通插入符用CreateSolidCaret(),而创建位图插入符用CreateCaret()函数.

2:路径层(Path)的概念!

3:我试图写一个MyNotePad的小应用程序,已完成的任务有:设置自定义的图标,窗口背景,光标.在窗口显示插入符,并让插入符随鼠标的点击而显示在相应的位置,用TextOut完成串的输入,显示,并保存于一个CString对象中,可是我发现显示文本的背景色(默认是白色)与自定义的窗口背景色不一致,我先是用如下的代码:
hdc.SetBkColor(hdc.GetBkColor));
来设置文本背景色,可是背景色仍然是默认的白色,我不停地想是不是SetBkcolor()不可以用,但当时真是笨,明明用GetBkColor()取到的就是文本的背景色,你再设置回去,那不就相当于什么都没做嘛!当时脑子中充斥的想法是以为GetBkColor()取到的是窗口的背景色呢!呵呵,笨!另外,hdc.SetTextColor()可以设置文本的颜色.但还有一个问题:下面是我处理退格键的代码:
void CMyNotePadView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
if(0x0d==nChar)
{
// m_ptOrigin
}
else if(0x08==nChar)
{
CClientDC hdc(this);
hdc.SetBkColor(RGB(208,221,238));
hdc.SetTextColor(RGB(208,221,238));
hdc.TextOut(m_ptOrigin.x,m_ptOrigin.y,m_strLine);
m_strLine=m_strLine.Left(m_strLine.GetLength()-1);
}
else
{
m_strLine+=nChar;
}
::InvalidateRect(m_hWnd,NULL,FALSE);
CView::OnChar(nChar, nRepCnt, nFlags);
}
为了应付窗口切换时的重绘,我把输出工作放在了OnDraw()函数中了,别的都是正常的,可当退格的时候后面的那个残留的插入符没有消失,仍然保留着,我找了半天找不到原因,最后才发现原来调用::InvalidateRect()来启动OnDraw()时,第三个参数如果传FALSE,会导致原来无效区域的背景不刷新,然后我将之换成TRUE就OK了!

4:至于类似卡拉OK字幕的平滑变色效果,可以用DrawText()来实现.

<< 五:菜单编程 >>
1:消息的分类及路由形态:
(1)标准消息:
除WM_COMMAND之外,所有以WM_开关的消息都是标准消息.
(2)命令消息:
来自菜单,加速键或工具栏按钮的消息.这类消息都是以WM_COMMAND呈现.在MFC中,通过菜单项的标识ID来区分不同的命令消息,在SDK中,则是以wParam参数识别.
(3)通告消息:由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生.这类消息也是以WM_COMMAND形式呈现.

关于消息的接收:
只要是从CWnd派生的类,都既可以接收标准消息,又可以接收命令消息以及通告消息.而其它从CCmdTarget派生的类,就只能接收命令消息与通告消息,而不能接收到标准消息.
另外,从视频教材的第一个菜单响应例子的实验证明,一个WM_COMMAND消息,是首先被CMainFrame类接收到,但它先交由它的子窗口View类处理,如果View类没有相应的处理函数,就由View类将此消息交由Doc类处理,如果Doc类也没有相应的处理函数,那么Doc类会将消息返还给View类,而View类再将消息返还给MainFrame类,这时才检查MainFrame类中有没有相应的消息处理函数,如果仍然没有的话,它就将消息上还给了App类处理(如果在这里仍没有处理函数,则回到CCmdTarget类,然后由FrameWork会将消息传递给::DefWindowProc()函数,注意这儿绕过了CWinThread,因为CWinThread并不参与消息路由!)并且一个消息一旦在某个类中被响应过,则不再接着传递!(关于这个看<<深入浅出MFC>>第9章的图9-4会很清楚!)
而对于一个标准WM_消息,则由CMyView类先处理,如果没有处理函数,则上溯到CView,如果仍没有处理函数,则上溯到CWnd类,如果仍没有处理函数,则上溯到CCmdTarget了.
这就是消息的路由过程!

2:如何在菜单项上加上对号标记:

在MainFrame类的OnCreate()中最后加入GetMenu()->GetSubMenu(3)->CheckMenuItem(0,MF_BYPOSITION | MF_CHECKED);
另外,在响应刷新消息的函数中由MFC传入的那个CCmdUI*指针也可以用于此!

3:如何在菜单项中设置默认菜单项:
同样在MainFrame类的OnCreate()的最后加入GetMenu()->GetSubMenu(3)->SetDefaultItem(0,TRUE);即可.
要注意,一个子菜单中只可以有一个默认(缺省)菜单项.以最后一个设置为默认的菜单项为准.

4:如何设计图形标记菜单:
同样在MainFrame类的OnCreate()的最后加入GetMenu()->GetSubMenu(3)->SetMenuItemBitmap(,,,);
要注意,一个菜单的图形标记只能是13*13大小的位图,这个数据可以通过GetSystemMetrics(SH_CXMENUCHECK);或者GetSystemMetrics(SH_CYMENUCHECK);来获取!

5:如何Enable或者Disable一个菜单项
同样在MainFrame类的OnCreate()的最后加入GetMenu()->GetSubMenu(3)->EnableMenuItem(,);
要注意,菜单项的Enable或者Disable功能的正常完成需要在MainFrame类的构造函数中将一个叫m_bAutoMenuEnable的成员变量设置为FALSE.但是如果做了m_bAutoMenuEnable=FALSE;后,会导致CMainFrame so no ON_UPDATE_COMMAND_UI or ON_COMMAND handlers are needed!

6:如何让一个菜单显示或不显示:
要让菜单不显示,在MainFrame类的OnCreate()的最后加入SetMenu(NULL);即可!
要让菜单显示,可以先构造一个CMenu对象,再利用该对象的LoadMenu(ID of menu)函数将之加载,然后再用SetMenu(该menu对象的指针)即可!
要注意,你的CMenu对象必须是一个定义在CMainFrame类中的成员变量,不能是一个在OnCreate()函数中临时定义的一个变量,因为该函数结束时其中的成员变量要发生析构,那么你的菜单就要出问题.或者你也可以定义临时函数内的局部变量,然后在SetMenu()完了后用一个menu.Detach()成员函数将菜单与菜单对象的关系切断,这样也是可以的.

7:MFC之于菜单项的命令更新机制:
菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI消息,谁捕获CN_UPDATE_COMMAND_UI消息,MFC就在其中创建一个CCmdUI对象.我们可以通过手工或者利用ClassWizard在消息映射中添加ON_UPDATE_COMMAND_UI宏来捕获CN_UPDATE_COMMAND_UI消息.
在后台所做的工作是:操作系统发出WM_INITMENUPOPUP消息,然后由MFC的基类如CFrameWnd接管.它创建一个CCmdUI对象,并与第一个菜单项相关联,调用对象的一个成员函数DoUpdate().这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针.同一个CCmdUI对象就设置为与第二个菜单项相关联,这样顺序进行,直到完成所有菜单项.
更新命令UI处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项目.
用我自己的话来讲就是:命令更新机制就是当无论何时要显示菜单项时,系统都会对CMainFrame类发出一个包含每个菜单项的UPDATE_COMMAND_UI消息,如果你在CMainFrame类中针对某个菜单项做了相应的消息处理函数,那么你可以在这个函数中检测程序中的某些条件,做出让某个菜单项显示或不显示或者变灰的决定!

8:如何制作右键弹出菜单:
方法一:在VC中,点菜单上的Project/Add To Project/Components and Controls...,在弹出的对话框中打开Visual C++ Components文件夹,再找到Pop-up Menu,选择Insert按钮,在随后的确认框中点确定,然后在随后的 Add pop-up menu to:下拉列表框中选择View类,资源ID可以不改,按OK,然后重新编译工程就可以看到结果了.
方法二:其实在方法一中,MFC在后台所做的就是在View类中加入了一个函数:OnContextMenu()函数如下:
void CMyNotePadView::OnContextMenu(CWnd*, CPoint point)
{
// CG: This block was added by the Pop-up Menu component

{
if (point.x == -1 && point.y == -1)
{ //keystroke invocation
CRect rect;
GetClientRect(rect);
ClientToScreen(rect);
point = rect.TopLeft();
point.Offset(5, 5);
}
CMenu menu;
VERIFY(menu.LoadMenu(CG_IDR_POPUP_MY_NOTE_PAD_VIEW));
CMenu* pPopup = menu.GetSubMenu(0);
ASSERT(pPopup != NULL);
CWnd* pWndPopupOwner = this;
while (pWndPopupOwner->GetStyle() & WS_CHILD)
pWndPopupOwner = pWndPopupOwner->GetParent();
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, pWndPopupOwner);
}
}
这就使得我们也可以模仿以上行为做出自己的弹出菜单,首先在View类中添加对右键的响应函数,在其中也写如下代码:
CMenu menu;
menu.LoadMenu(IDR_MENU1);//这个ID所对应的菜单你要事先做好
CMenu* pPopup=menu.GetSubMenu(0);
pPopup->TrackPopupMenu(PM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
就可以了.但是现在的菜单显示的位置很离谱,原来显示弹出菜单用得是屏幕坐标,而你在View类中捕获的位置坐标是窗口坐标,这时就
得用这个函数:ClientToScreen(point);在TrackPopupMenu()之前做个转换!这样就正常了.

9:右键弹出菜单项的响应:
在响应右键弹出菜单时,你可以在View类中响应,也可以在MainFrame类中响应,这主要取决于你在做pPopup->TrackPopupMenu()时的第四个
参数,也就是该弹出菜单的父窗口.如果如上是this,则只能在View类中响应,如果用GetParent()设其为MainFrame类,则既可以View类中响应,又可以在MainFrame类中响应.并且如果View类跟MainFrame类皆有响应函数,则优先响应View类,其实也就是说MainFrame中的响应函数将被忽略.所以为了方便,弹出菜单的父类就尽可能设为MainFrame类!

10:如何在菜单栏上动态增加,插入或删除一个子菜单
因为以前菜单栏上的子菜单都是在菜单编辑器中事先做好的,而现在要求在程序运行期间按需添加一个子菜单到指定位置.可以用如下代码段实现:
CMenu menu;
menu.CreatePopupMenu();
GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"动态菜单");
menu.Detach();
如上的代码会将新子菜单放在所有已有子菜单项的后面,如果要想在指定的位置插入子菜单,则应该用InsertMenu()而不是AppendMenu(),示例如下:
CMenu menu;
menu.CreatePopupMenu();
GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"动态菜单");
menu.Detach();
menu.CreatePopupMenu();
GetMenu()->InsertMenu(2,MF_BYPOSITION | MF_POPUP,(UINT)menu.m_hMenu,"插入菜单");
menu.Detach();
要在新加入的子菜单下面显示菜单项的示例代码如下:
CMenu menu;
menu.CreatePopupMenu();
GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"动态菜单");
menu.Detach();
menu.CreatePopupMenu();
GetMenu()->InsertMenu(2,MF_BYPOSITION | MF_POPUP,(UINT)menu.m_hMenu,"插入菜单");
menu.AppendMenu(MF_STRING,111,"How");
menu.AppendMenu(MF_STRING,112,"I");
menu.AppendMenu(MF_STRING,113,"Feel?");
menu.Detach();
要在已有子菜单中添加菜单项同样可以用AppendMenu()或者InsertMenu()它们的区别自然是前者在最后放置,后者由你指定位置.示例代码如下:
GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,114,"动态");
GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN,MF_BYCOMMAND | MF_STRING,115,"安岳");
以上函数中若使用MF_SEPARATOR参数可以插入分割符!
要删除一个子菜单或者菜单项可以使用DeleteMenu()函数.如果调用它的是菜单栏,则删除的是子菜单,如果调用它的是子菜单,则删除的是菜单项.

至于这种动态菜单项的命令响应则不能再借助于ClassWizard啦,跟手动添加对其它消息的响应一样,需要三个步骤:
一:添加响应函数声明
在CMainFrame的构造函数中,紧随系统原有的afx_msg int OnCreate();之类的语句之后写上:
afx_msg void OnHow();但注意不要写在括住系统自己的响应函数的//{{AFx_MSG...及//}}AFX_MSG之内了.
二:添加消息映射
在MainFram.cpp中找到以(CMainFrame,CFrameWnd)为参数的BEGIN_MESSAGE_MAP,END_MESSAGE_MAP宏对,在其中添加: ON_COMMAND(IDM_HOW,OnHow)注意这一句是宏语句,不要在其后面加分号.(也注意不要写在//{{AFx_MSG...及//}}AFX_MSG之内了.)因为这个宏中需要ID,所以自己还要手动的在Resource.h中加入:#Define IDM_HOW 111 一句.(111是AppendMenu()时指定的ID).
三:完成消息响应函数
在MainFrame.cpp文件后面写如下代码段:

void CMainFrame::OnHow()
{
MessageBox("Hey man,what are you doing!");
}
即完成了此命令消息响应.

11:在本课的最后,为了实现一个在CMainFrame中截获WM_COMMAND消息,而不让它下流到View 类中去的方法:通过重载CWnd::OnCommand()函数!还介绍了一个非常典型的头文件包含编译错误的解决方法,LOWORD()宏的使用!以及如何在CMainFrame类的成员函数中调用CView类中的成员变量的问题-GetActiveView()的使用!

<< 六:对话框编程 >>

1:分清模态,非模态,系统模态对话框!

2:在View类的cpp中响应菜单消息,利用已有的对话框类来产生对话框别忘了在该cpp中包含相应定义对话框类的头文件.

3:制作非模态对话框要使用成员变量或者堆上分配的对象,不能在函数内定义一个局部变量来用.示例代码如下:
dlg.Create(IDD_MYDIALOG,this);
dlg.ShowWindow(SW_SHOW);

4:注意一个模态对话框当点击其中的OK按钮时这个对话框是被Destroy了的,而对于一个非模态对话框而言,它只是隐藏了,而并未被Destroy,所以如果你定义了一个非模态对话框,并使用了原有的OK按钮,你一定要重载其基类CDialog的OnOK()函数,在其中自己调用DestroyWindow().详见MSDN中CDialog::OnOK()函数的讲解页!

5:静态文本控件的消息响应要注意一两点:
一:要为静态文本控件指定具体的ID,而不能是默认的IDC_STATIC;
二: 要在静态文本控件的属性页中的Styles页中将Notify前面的勾打上,才能实现消息响应.
下面是一段静态文本控件的操作代码:(设置静态文本的内容);
DWORD elapsedTime;
elapsedTime=GetTickCount()/1000; //get seconds
UINT hours=elapsedTime/3600;
UINT minutes=elapsedTime%3600/60;
UINT seconds=elapsedTime%3600%60;
char times[100];
sprintf(times,"系统已运行%d小时%d分钟%d秒!",hours,minutes,seconds);
GetDlgItem(IDC_TIME)->SetWindowText(times);
是在对话框类的成员函数中所做的.

6:获取控件文本的几种方法:
一:GetDlgItem()->GetWindowText();
二:GetDlgItemText();
三:GetDlgItemInt();这个函数取到控件文本并将之转换成int返回给调用者.这对如使用EDIT控件获取整数非常方便,并且它可以有效处理有符号数.这个函数的调用稍有复杂,查阅MSDN!
四:将控件与成员变量相关联,比如要从EDIT控件获取整数,可以直接将EDIT控件与int类型的变量相关联,它会直接获取整数,并自动做相关的输入数据的类型检测,当然也可以关联CEdit型变量,再间接获取数据,用这种方法时要注意UpdateData()函数的适时调用!如果是CEdit控件,可以用CEdit中的GetWindowText()函数获取其内容!
五:通过发送WM_GETTEXT或者WM_SETTEXT消息的方式获取:
分为三种情况:
(1)使用合全局的::SendMessage(),如下:
::SendMessage(GetDlgItem(IDC_EDIT1)->m_hWnd,WM_GETTEXT,10,(LPARAM)ch1);//ch1是一个字符数组
或者是:
::SendMessage(m_edit1.m_hWnd,WM_GETTEXT,10,(LPARAM)ch1); //这是已关联控件变量的情况下
(2)使用CWnd的SendMessage(),如下:
GetDlgItem(IDC_EDIT1)->SendMessage(WM_GETTEXT,10,(LPARAM)ch1);
或者是:
m_edit1.SendMessage(WM_GETTEXT,10,(LPARAM)ch1);
(3):使用CWnd::SendDlgItemMessage()函数,它实际上相当于先用GetDlgItem()得到控件句柄,再SendMessage(),是一种组合的方便方式!如:
SendDlgItemMessage(IDC_EDIT1,WM_GETTEXT,10,(LPARAM)ch1);
在讲这个的时候,还讲到了可以获得一个控件中选中的内容的消息-EM_GETSEL以及可以设置哪些内容被选中的EM_SETSEL消息.使用方法见MSDN.如:
SendDlgItemMessage(IDC_EDIT1,EM_SETSEL,1,3);
m_edit1.SetFocus();//这句的意义是因为如果该控件不是当前的FOCUS所在,那么即使显示了选中也会不可见.

7:实现对话框收缩与扩展功能的代码示例:
void CTestDlg::OnButton1()
{
// TODO: Add your control notification handler code here
CString str;
GetDlgItemText(IDC_BUTTON1,str);
static CRect rectLardge;
static CRect rectSmall;
if(rectLardge.IsRectNull())
{
CRect rectSeparator;
GetWindowRect(&rectLardge);
GetDlgItem(IDC_SEPARATOR)->GetWindowRect(&rectSeparator);
rectSmall.top=rectLardge.top;
rectSmall.left=rectLardge.left;
rectSmall.right=rectLardge.right;
rectSmall.bottom=rectSeparator.bottom;
}
if(str=="收缩<<")
{
SetDlgItemText(IDC_BUTTON1,"扩展>>");
SetWindowPos(NULL,0,0,rectSmall.Width(),rectSmall.Height(),
SWP_NOMOVE | SWP_NOZORDER);

}
else
{
SetDlgItemText(IDC_BUTTON1,"收缩<<");
SetWindowPos(NULL,0,0,rectLardge.Width(),rectLardge.Height(),
SWP_NOMOVE | SWP_NOZORDER);
}
}

8:这节课最后讲了如何实现回车时让输入焦点在各个控件之间遍历的方法,其中涉及到了默认键消息响应函数的操作,以及如何用SetWindowLong()来改变一个控件的窗口响应函数的方法.

<< 七:对话框编程之二 >>
1:要改变对话框内控件上的文本字体,要在对话框的属性对话框中设置,而不是在单个对话框的属性中设置!

2:逃跑按钮的制作
放置两个外观一样的按钮,初始化为隐藏其中的一个,在对话框类中响应MouseMove消息,一旦检测到鼠标位于某个当前正在显示的按钮上时,就隐藏之,将另一个按钮显示出来.这样给用户的感觉就是只有一个按钮,在躲藏你的鼠标!
而录像中的做法是新建一个继承自CButton的类,然后将两个按钮分别与该新类的一个对象所关联,然后由这个类实现对MouseMove的响应,并在每个对象中放置一个指向本类的公有成员指针变量,指向另一个对象,这样,在响应函数中很方便地实现了ShowWindow(SW_HIDE),以及ShowWindow(SW_SHOW)操作.至于那两个对象中指向对方的指针的初始化可以放在控件所在对话框类的构造函数中,也可以该对话框类中加入一个对WM_INITDIALOG的消息的响应函数,将初始化放置于其中!

3:往对话框或其它地方插入组合框(combo box)资源时,初始放置时一定要将它拖大一些,要不然放置好以后其高度无法修改,而且下拉出的列表框部分无法显示出来,只能重新加入一个!

4:关于属性表单对话框的制作,
一:要插入属性页,并做好控件布局;
二:要为每个属性页关联相应的继承自CPropertyPage类的自定义类.
三:要在类视图中点根工程图标右键,加入一个类型为MFC Class的新类,其基类为CPropertySheet.
四:在该类中分别加入每个属性页类的一个对象.(注意头文件的包含)
五:在该类的两个构造函数中都通过AddPage()函数加载每个属性页对象.
六:在某个菜单项的响应函数中定义一个该类的对象(其参数为属性表单的标题),然后调用该类的成员函数:DoModal()创建一个模态的表单,或者Create()创建一个非模态的表单.(注意头文件的包含);

5:关于向导对话框的制作:
向导对话框实际是属性表单的一种变体,只要在属性表单的DoModal()调用生成属性表单之前加上一句:propsheet.SetWizardMode();生成的就是向导对话框而不是表单了.
如何去掉默认生成向导对话框第一页中的上一步按钮:
在该页的类中增加一个虚函数 OnSetActive(),在其中加入如下语句:
((CPropertySheet*)GetParent())->SetWizardButtons(PSWIZB_NEXT);即可.
如何去掉默认生成向导对话框最后一页的下一步按钮,并且加上结束按钮:
同上理: 在相应的类中增加虚函数 OnSetActive(),加入如下语句:
((CPropertySheet*)GetParent())->SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH);
注意:如果你在第一页中将上一步按钮去掉了,则后面每一页的上一步按钮都没有了(我觉得这样不太合理)所以你要用同样的方法设置每一个属性页,在其中设置合适的按钮!

6:本次课程中提到了用C语言函数memset初始化数组的一种方法很好,如:
memset(m_bLike,0,sizeof(m_bLike));

<<八:界面修改,工具栏,状态栏,启动画面的制作>>

1:如何修改单文档应用程序的窗口标题,查阅MSDC文章:Changing the styles of a window created by MFC.
要在MainFrame的PrecreatWindow()中加入如下代码:
cs.style&=~FWS_ADDTOTITLE;
cs.lpszName="This is a test!";
可以先不要上一句试一试!
另一种方法是:
cs.style=WS_OVERLAPPEDWINDOW;
再进行修改,也可以不修改,那么是去掉默认文档标题,而只显示原程序标题!

另一类方法是在窗口创建后再修改,因为在OnCreate中,开始的这些代码:
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar/n");
return -1; // fail to create
}

if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar/n");
return -1; // fail to create
}
// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);

完成了窗口创建,工具栏,状态栏的创建等工作,可以在后面利用一个系统全局函数SetWindowLong()函数进行修改:

加入代码为:SetWindowLong(m_hWnd,GWL_STYLE,WS_OVERLAPPEDWINDOW);
与此相对,还有一个GetWindowLong()函数可供使用!如下面代码去掉了窗口上的最大化按钮:
SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_hWnd,GWL_STYLE) & ~MAXIMIZEBOX);
当然SetWindowLont()还可以做别的修改.与SetWindowLong()相类似的另一个系统全局函数为SetClassLong();

2:如何完成一个动画图标
其实就是准备好几个图标,在定时器消息响应中更改图标即可完成.
第一步是准备好几个(如三个)图标.
第二步是在MainFrame类中做三个图标类的相关对象的成员变量,或者是一个大小为3的HICON数组.
第三步是在MainFrame类的OnCreate()函数中LoadIcon()进行对三个图标的加载.其中用到的实例句柄的获取有三种方法:
一:用全局函数AfxGetInstanceHandle()获取,
二:先在MainFrame文件中用extern声明一下全局对象theApp,然后使用theApp.hInstance;
三:使用全局函数AfxGetApp()获取全局对象theApp对象的指针,然后用AfxGetApp()->hInstance;
第二个参数是一个字符指针,可我们只有图标的资源ID,所以要进行必要的转换:用MAKEINTRESOURCE宏!
第四步是设置定时器,也在OnCreate()函数中定义:SetTimer(1,1000,NULL);
第五步是在MainFrame中添加WM_TIMER消息响应,在其中加入代码:
static int index=0;
SetClassLong(m_hWnd,GCL_HICON,(LONG)m_hIcons[index]);
index=++index%3;

3:在工具栏上新加一个按钮,要让它与前一个按钮之间有一个分隔符,只需要将它轻轻向一旁拖动一点点再放开即可,而要删除工具栏上的一个按钮,你只是选中它再按DEL键是完不成的,它只是将按钮上的图案删除,所以删除一个按钮要将它拖动到工具栏之外,再松手!

4:如何创建一个工具栏
在MSDN 的关于CToolBar的讲解页有详细说明!
一:在CMainFrame中加入一个CToolBar类对象的成员变量,
二:插入工具栏资源,
三:在CMainFrame的OnCreate()中加入:
if (!m_MyToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_MyToolBar.LoadToolBar(IDR_MYTOOLBAR))
{
TRACE0("Failed to create toolbar/n");
return -1; // fail to create
}

m_MyToolBar.EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_MyToolBar);
各个函数调用及参数传递查看MSDN!

5:如何让一个工具栏隐藏或显示:
if(m_MyToolBar.IsWindowVisible())
{
m_MyToolBar.ShowWindow(SW_HIDE);
}
else
{
m_MyToolBar.ShowWindow(SW_SHOW);
}
但这样做的结果是工具栏虽说隐藏了,但是工具条还在,所以还要在后面加上一句:
ReCalcLayout();
这样做还是有问题,如果工具栏没有停靠在边上而是一个单独的小窗口,那么只做上面的工作只使得工具栏上的按钮不见了,而那个小窗口还在,所以,还要调用一个函数:
DockControlBar(&m_MyToolBar);
经过上面这句,小窗口也如愿消失了,但问题还有一点,就是当用户将工具栏放置为一个小窗口时,再点击菜单,要让这个工具栏显示出来,当然我们应该将工具栏仍按用户先前的小窗口样式显示出来比较好,可是这次工具栏又自动停靠在客户区顶部了?这个功能如何实现呢?孙老师只是提示可以查MSDN中CToolBar的成员函数解决这个问题,并没细讲,所以我看了MSDN,发现有两个函数:CToolBar::IsFloating()利用这个函数可以判断一个工具栏是否处于浮动状态,另一个是CFrameWnd::FloatControlBar()这个函数可以让一个控制栏处于浮动状态,然后我在CMainFrame中加入了一个BOOL型的成员变量,在每次判断工具栏是否可见时用来记录工具栏是否处于浮动状态,然后在重新生成工具栏时根据它的置决定是否将工具栏设为浮动状态,但是第二个函数好像不太好使,所以我又换用了SetWindowPos()成员函数,可是也不能将它放置为一个独立的小窗口.
显示和隐藏工具栏的第二种方法:
用一个函数:CFrameWnd::ShowControlBar(),因为这个函数的固有特性,上面是if...else...判断就可以简化为一句代码:
ShowControlBar(&m_MyToolBar,!m_MyToolBar.IsWindowVisible(),FALSE);
并且我惊讶的发现,用这个函数时,上面提到的浮动工具栏让它在恢复的时候仍回复为浮动的问题自动解决了!哈哈,好.

6:状态栏相关编程
因为MFC自动生成的系统已经包含了一个状态栏,所以我们暂时仅限于已有状态栏的修改,而不是另外生成一个状态栏.
状态栏最左边的那一长条,就是经常显示一些提示字符串的那部分叫做提示行,而右侧那三个小窗口是用来指示CapsLock,ScrollLock,NumLock开关的状态,称为状态指示器.
状态栏跟工具栏一样,也是在CMainFrame类中定义并在OnCreate()中创建的.
下面的代码在状态指示器的最左边放置了两个小窗口,并在第一个小窗口中放置了一个时钟:
同样的CMainFrame的OnCreate()中,
CTime tm=CTime::GetCurrentTime();
CString strTime=tm.Format("%H:%M:%S");

CClientDC dc(this);
CSize sz=dc.GetTextExtent(strTime);
m_wndStatusBar.SetPaneInfo(1,IDS_TIMER,SBPS_NORMAL,sz.cx);//调整窗口大小
m_wndStatusBar.SetPaneText(1,strTime);
SetTimer(2,1000,NULL);
当然要先有准备工作 ,在字符串资源中添加两个字符串,ID分别为:IDS_TIMER,IDS_PROGRESS,交将之添加到CMainFrame.cpp的
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
IDS_TIMER,
IDS_PROGRESS,
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
这个数组中,并且上面SetPaneInfo()中的第一个参数就是相应ID在indicators[]数组中的索引值,显然最后三个ID是大小写,数字锁及ScroolLock的标识.
做完上面这些工作,这个时钟还不能变化,只是显示了一个定值,我们还要在定时器的响应函数中再刷新该值:
if(2==nIDEvent)
{
CTime tm=CTime::GetCurrentTime();
CString strTime=tm.Format("%H:%M:%S");
m_wndStatusBar.SetPaneText(1,strTime);
}
这样就完成了一个在状态栏中显示的,可以动态变化的时钟!

7:如何创建一个进度条并将之放置在状态栏中的某个位置?
与MFC中其它标准资源一样,进度条也有一个专门的类与之相对应:CProgressCtrl类.
先在CMainFrame中放置一个成员变量:CProgressCtrl m_ProgBar;
然后在OnCreate()中加入:
m_ProgBar.Create(WS_CHILD | WS_VISIBLE,CRect(300,100,500,120),this,88888);
就可以在窗口中显示一个进度条.
至于要将之放置于状态栏上的某个窗格之中,就要先得到窗格所在的矩形区域,然后在上面是第二个参数中进行指定,故将上面一句改为如下:
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,this,88888);
可是这样并未实现设置,所以我们在上面设置断点,发现到达m_ProgBar.Create()时矩形区域的值并不正常,原来在OnCreate()未结束时,状态栏的设置无法完成,无法获得矩形区域位置,所以,就要想办法在OnCreate()结束时做上面的工作,可以做一个自定义消息,在OnCreate()末尾发送一个该消息,并做一个消息响应函数,然后将上面的代码放置在其中即可.
要自定义消息,首先要在CMainFrame的头文件首部做:
#define WM_PROGRESS WM_USER+1
WM_USER是一个系统定义宏,详细情况查MSDN.
然后在CMainFrame的头文件的消息响应中加入:消息响应函数的声明:
afx_msg void OnProgress();
再加入消息映射:
ON_MESSAGE(WM_PROGRESS,OnProgress)
再实现消息响应函数:
void CMainFrame::OnProgress()
{
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,&m_wndStatusBar,88888);//注意,这里父窗口就不能设置为this了,而要是状态栏!
m_ProgBar.SetPos(50);
}
当然,不能忘了在OnCreate()最后发送消息:
PostMessage(WM_PROGRESS);

这样就可以了!
但是问题还是有的,你会发现当你拉动窗口改变其大小时,状态栏的位置发生了变化,不再覆盖在先关状态栏的那个小窗口上了,怎么办呢?你会想到在窗口大小改变时,系统会接受到一个WM_PAINT消息,只要在那个消息的响应函数中实时获取小窗口的矩形区域,再改变进度条的位置,不就可以了吧,没错,这样的确可以,又因为WM_PAINT消息当窗口显示时,也就是说OnCreate()之后就会马上收到,所以我们也不用像上面那样麻烦的自定义什么消息啦,直接在CMainFrame中加入WM_PAINT的消息响应函数,并在其中加入:
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,&m_wndStatusBar,88888);
m_ProgBar.SetPos(50);
即可了,但是问题又来了,当窗口大小一改变时,程序发生了一个致使错误,原来我们不能在每一次响应时都创建进度条,进度条对象只有一个,哪能多次使用呢?所以,改为如下代码:
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
if(!m_ProgBar.m_hWnd)
{
m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,&m_wndStatusBar,88888);
}
else
{
m_ProgBar.MoveWindow(rect);
}
m_ProgBar.SetPos(50);
这下,无论怎么拖,也不会发生异常现象了!
那么,如何让进度条动起来呢?相关的成员函数为
SetStep(),以及StepIt();

8:下面的代码将鼠标当前的位置坐标显示在状态栏的提示行中:

在View类中响应WM_MOUSEMOVE消息,在其中加入代码如下:

CString str;
str.Format("x=%d,y=%d",point.x,point.y);
((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);

这段代码要正常运行的前提是首先CMainFrame在View文件中不可见,所以要先包含一个MainFrame.h头文件,然后 ,因为m_wndStatusBar在CMainFrame中是一个protected 变量,所以不能在View类中访问,所以我们要手动把它的访问权限改为public.然后就OK了!
其实还有一种更方便的方法:调用CFrameWnd::SetMessageText()
所以上面的代码就可以改为:
CString str;
str.Format("x=%d,y=%d",point.x,point.y);
((CMainFrame*)GetParent())->SetMessageText(str);
这样子,就不用取得状态栏指针了,也就不用去修改其访问权限了!
关于这个工作,还有第三种方法,也不用去修改状态栏的访问权限,这是应用了另一个函数:CHtmlView::GetStatusBar(),上面的代码就可以是:
CString str;
str.Format("x=%d,y=%d",point.x,point.y);
((CMainFrame*)GetParent())->GetMessageBar()->SetWindowText(str);
下面还有第四种方式:这是应用了又一个函数:CWnd::GetDescendantWindow(),这个函数可以根据窗口ID来从调用它的窗口出发找到与该ID相同的一个子孙窗口的指针:
CString str;
str.Format("x=%d,y=%d",point.x,point.y);
GetParent()->GetDescendantWindow(AFX_IDW_STATUS_BAR)->SetWindowText(str);
至于上面那个ID号,是从CStatusBar::Create()中看到它!关于GetDescendantWindow()的调用,要注意其第二个参数在MSDN中关于临时窗口及持久窗口的讲解!

9:关于程序的启动画面的制作
这个跟往程序中加入右键PopUp菜单一样,可以使用VC的组件库中的组件.找到一个叫SplashScreen的东西,加入它到工程中,别的什么也别动,先编译运行一下,你会发现程序已经有一个启动画面了,不过是很简陋的.
这个组件在我们的程序中加入了一副默认位图,并加入了一个叫CSplashWnd的类,并且在CMainFrame的OnCreate()函数中加入了一句:CSplashWnd::ShowSplashScreen(this),进行了相应的启动工作.
在CSplashWnd的成员函数OnCreate()中有一句SetTimer(1, 750, NULL);其消息响应函数中进行了启动画面的隐藏,所以,修改其中的时间值可以修改启动画面显示的时间长度!
另外,可以发现窗口的显示与启动画面的显示是同时进行的,如何才能让画面消失时窗口才显示出来呢?

至于启动画面的改变,可以自己插入位图,然后将它的ID修改为IDB_SPLASH然后RebuildAll,也可以在其源文件中LoadBitmap()中将ID改为自己想要加载的位图的ID!

我在网上看到一个人写的启动画面制作的示例程序,一个基于对话框的程序,但并没有使用这个组件,是它自己写的一个类,我目前还看不大懂 :(
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: