1、Visual C+编程的若干技巧Visual C+是一种面向对象的可视化编程工具,它提供的 AppWizard 能自动生成应用程序的标准框架,大大减轻了编程的工作量。但是,由于生成的应用程序框架使程序的用户界面和程序结构都只能具有标准形式,而在实际的程序设计中,又常常需要设计一些非标准的应用程序,或实现一些特别的功能或操作。因此使用一些技巧来修改应用程序框架,以得到所需的应用效果,便很有使用价值。本文将主要介绍如下的编程技巧:修改主窗口风格、创建不规则形状窗口、用鼠标单击窗口标题条以外区域移动窗口、使用上下文菜单、使应用程序只能运行一个实例、使应用程序显示为任务条通知区中的图标和显示旋转文本等
2、。1. 修改主窗口风格AppWizard 生成的应用程序框架的主窗口具有缺省的窗口风格,比如在窗口标题条中自动添加文档名、窗口是叠加型的、可改变窗口大小等。要修改窗口的缺省风格,需要重载 CWnd:PreCreateWindow(CREATESTRUCT / 创建窗口的基本参数HANDLE hInstance; / 拥有将创建的窗口的模块实例句柄HMENU hMenu; / 新窗口的菜单句柄HWND hwndParent; / 新窗口的父窗口句柄int cy; / 新窗口的高度int cx; / 新窗口的宽度int y; / 新窗口的左上角 Y 坐标int x; / 新窗口的左上角 X 坐标L
3、ONG style; / 新窗口的风格LPCSTR lpszName; / 新窗口的名称LPCSTR lpszClass; / 新窗口的窗口类名DWORD dwExStyle; / 新窗口的扩展参数 CREATESTRUCT;CREATESTRUCT 结构的 style 域定义了窗口的风格。比如,缺省的 MDI 主窗口的风格中就包括 FWS_ADDTOTITLE(在标题条中显示当前的工作文档名)、FWS_PREFIXTITLE(把文档名放在程序标题的前面)、WS_THICKFRAME(窗口具有可缩放的边框)等风格。由于多种风格参数由逻辑或(“|”)组合在一起的,因此添加某种风格,就只需用“|”
4、把对应的参数加到 CREATESTRUCT 结构的style 域中;删除已有的风格,则需用“ /去除标题条中的文档名cs.style /去除可改变大小的边框cs.style |= WS_DLGFRAME; /增加不能改变大小的边框/ 确定主窗的大小和初始位置int cxScreen = :GetSystemMetrics(SM_CXSCREEN);/获得屏幕宽int cyScreen = :GetSystemMetrics(SM_CYSCREEN); /获得屏幕高cs.x = 0; / 主窗位于左上角cs.y = 0;cs.cx = cxScreen/2; / 主窗宽为 1/2 屏幕宽cs.c
5、y = cxScreen/2; / 主窗高为 1/2 屏幕高return CMDIFrameWnd:PreCreateWindow(cs);2. 创建不规则形状窗口标准的 Windows 窗口是矩形的,但在有些时候我们需要非矩形的窗口,比如圆形的、甚至是不规则的。借助 CWnd 类的 SetWindowRgn 函数可以创建不规则形状窗口。CWnd:SetWindowRgn 的函数原型如下:int SetWindowRgn( HRGN hRgn, / 窗口区域句柄BOOL bRedraw ); / 是否重画窗口CRgn 类封装了关于区域的数据和操作。通过(HRGN)强制操作可以从 CRgn 类中
6、取得其 HRGN 值。CRgn 提供了 CreateRectRgn、CreateEllipticRgn 和 CreatePolygonRgn 成员函数,分别用以创建矩形、(椭)圆形和多边形区域。创建非矩形窗口的方法如下:首先,在窗口类中定义区域类成员数据(如 CRgn m_rgnWnd);其次,在窗口的 OnCreate 函数或对话框的 OnInitDialog 函数中调用CRgn 类的 CreateRectRgn、CreateEllipticRgn 或 CreatePolygonRgn 函数创建所需的区域,并调用 SetWindowRgn 函数。下例将生成一个椭圆窗口。1. 在 Develo
7、per Studio 中选取 File 菜单中的 New 命令,在出现的 New 对话框中选择创建 MFC AppWizard(exe)框架应用程序,并输入项目名为 EllipseWnd。设定应用程序类型为基于对话框(Dialog based),其它选项按缺省值创建项目源文件。2. 使用资源编辑器从主对话框(ID 为 IDD_ELLIPSEWND_DIALOG)删除其中的所有控制,并从其属性对话框(Dialog Properties)中设定其风格为 Popup、无标题条和边框。3. 在 EllipseWndDlg.h 源文件中给主对话框类 CEllipseWndDlg 增加一个 CRgn类保护
8、型数据成员 m_rgnWnd,它将定义窗口的区域。4. 在 EllipseWndDlg.cpp 源文件中修改主对话框类 CEllipseWndDlg 的OnInitDialog()函数,增加 m_rgnWnd 的创建,并将其定义为窗口区域。粗体语句为新增部分。BOOL CEllipseWndDlg:OnInitDialog()CDialog:OnInitDialog();/ Add “About.“ menu item to system menu./ IDM_ABOUTBOX must be in the system command range.ASSERT(IDM_ABOUTBOX AS
9、SERT(IDM_ABOUTBOX AppendMenu(MF_SEPARATOR);pSysMenu-AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);/ Set the icon for this dialog. The framework does this automatically/ when the applications main window is not a dialogSetIcon(m_hIcon, TRUE); / Set big iconSetIcon(m_hIcon, FALSE); / Set small ico
10、n/ 设置窗口标题为“椭圆窗口”,虽然对话框没有标题条,/ 但在任务条的按钮中仍需要标题SetWindowText(_T(“椭圆窗口“);/ 取得屏幕宽、高int cxScreen = :GetSystemMetrics(SM_CXSCREEN);int cyScreen = :GetSystemMetrics(SM_CYSCREEN);/ 设置椭圆 X、Y 方向的半径int nEllipseWidth = cxScreen/8;int nEllipseHeight = cyScreen/8;/ 将窗口大小设为宽 nEllipseWidth,高 nEllipseHeight/ 并移至左上角Mo
11、veWindow(0, 0, nEllipseWidth, nEllipseHeight);/ 创建椭圆区域 m_rgnWndm_rgnWnd.CreateEllipticRgn(0, 0, nEllipseWidth, nEllipseHeight);/ 将 m_rgnWnd 设置为窗口区域SetWindowRgn(HRGN)m_rgnWnd, TRUE);return TRUE; / return TRUE unless you set the focus to a control3. 用鼠标单击窗口标题条以外区域移动窗口移动标准窗口是通过用鼠标单击窗口标题条来实现的,但对于没有标题条的窗
12、口,就需要用鼠标单击窗口标题条以外区域来移动窗口。有两种方法可以达到这一目标。方法一:当窗口确定鼠标位置时,Windows 向窗口发送 WM_NCHITTEST 消息,可以处理该消息,使得只要鼠标在窗口内,Windows 便认为鼠标在标题条上。这需要重载CWnd 类处理 WM_NCHITTEST 消息的 OnNcHitTest 函数,在函数中调用父类的该函数,如果返回 HTCLIENT,说明鼠标在窗口客户区内,使重载函数返回 HTCAPTION,使Windows 误认为鼠标处于标题条上。下例是使用该方法的实际代码:UINT CEllipseWndDlg:OnNcHitTest(CPoint p
13、oint) / 取得鼠标所在的窗口区域UINT nHitTest = CDialog:OnNcHitTest(point);/ 如果鼠标在窗口客户区,则返回标题条代号给 Windows/ 使 Windows 按鼠标在标题条上类进行处理,即可单击移动窗口return (nHitTest=HTCLIENT) ? HTCAPTION : nHitTest;方法二:当用户在窗口客户区按下鼠标左键时,使 Windows 认为鼠标是在标题条上,即在处理 WM_LBUTTONDOWN 消息的处理函数 OnLButtonDown 中发送一个wParam 参数为 HTCAPTION,lParam 为当前坐标的
14、WM_NCLBUTTONDOWN 消息。下面是使用该方法的实际代码:void CEllipseWndDlg:OnLButtonDown(UINT nFlags, CPoint point) / 调用父类处理函数完成基本操作CDialog:OnLButtonDown(nFlags, point);/ 发送 WM_NCLBUTTONDOWN 消息/ 使 Windows 认为鼠标在标题条上PostMessage(WM_NCLBUTTONDOWN,HTCAPTION, MAKELPARAM(point.x, point.y);4. 使用上下文菜单Windows 95 应用程序支持单击鼠标右键弹出上下文
15、菜单的功能,这可通过处理WM_CONTEXTMENU 消息来实现。当在窗口内单击鼠标右键时,窗口将接收到 WM_CONTEXTMENU 消息,在该消息的处理函数内装载上下文菜单,并调用 CMenu:TrackPopupMenu 函数便可显示上下文菜单。CMenu:TrackPopupMenu 函数的原型如下:BOOL TrackPopupMenu( UINT nFlags, / 显示和选取方式标志int x, int y, / 显示菜单的左上角坐标CWnd* pWnd, / 接收菜单操作的窗口对象LPCRECT lpRect = NULL ); / 敏感区域为了使用上下文菜单,首先应在资源编辑
16、器中编制好上下文菜单,假设上下文菜单名为 IDR_MENU_CONTEXT;其次,用 ClassWizard 给窗口增加处理消息WM_CONTEXTMENU 的函数 OnContextMenu,以及各菜单命令的处理函数;然后编写相应的代码。下面的是 OnContextMenu 函数的代码实例:void CEllipseWndDlg:OnContextMenu(CWnd* pWnd, CPoint point) CMenu menu;/ 装入菜单menu.LoadMenu(IDR_MENU_CONTEXT);/ 显示菜单menu.GetSubMenu(0)-TrackPopupMenu(TPM_
17、LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON, point.x, point.y, this);5. 使应用程序只能运行一个实例Windows 是多进程操作系统,框架生成的应用程序可以多次运行,形成多个运行实例。但在有些情况下为保证应用程序的安全运行,要求程序只能运行一个实例,比如程序要使用只能被一个进程单独使用的特殊硬件(例如调制解调器)时,必须限制程序只运行一个实例。这里涉及两个基本的问题,一是在程序的第二个实例启动时,如何发现该程序已有一个实例在运行,而是如何将第一个实例激活,而第二个实例退出。对于第一个问题,可以通过给应用程序设置信号量,实例启动时首
18、先检测该信号量,如已存在,则说明程序已运行一个实例。第二个问题的难点是获取第一个实例的主窗对象指针或句柄,然后便可用SetForegroundWindow 来激活。虽然 FindWindow 函数能寻找正运行着的窗口,但该函数要求指明所寻找窗口的标题或窗口类名,不是实现通用方法的途径。我们可以用 Win 32 SDK 函数 SetProp 来给应用程序主窗设置一个特有的标记。用GetDesktopWindow 可以获取 Windows 系统主控窗口对象指针或句柄,所有应用程序主窗都可看成该窗口的子窗口,即可用 GetWindow 函数来获得它们的对象指针或句柄。用 Win 32 SDK 函数
19、GetProp 查找每一应用程序主窗是否包含有我们设置的特定标记便可确定它是否我们要寻找的第一个实例主窗。使第二个实例退出很简单,只要让其应用程序对象的 InitInstance 函数返回 FALSE 即可。此外,当主窗口退出时,应用 RemoveProp 函数删除我们为其设置的标记。下面的 InitInstance、OnCreate 和 OnDestroy 函数代码将实现上述的操作:BOOL CEllipseWndApp:InitInstance()/ 用应用程序名创建信号量HANDLE hSem = CreateSemaphore(NULL, 1, 1, m_pszExeName);/ 信
20、号量已存在?/ 信号量存在,则程序已有一个实例运行if (GetLastError() = ERROR_ALREADY_EXISTS)/ 关闭信号量句柄CloseHandle(hSem);/ 寻找先前实例的主窗口HWND hWndPrevious = :GetWindow(:GetDesktopWindow(), GW_CHILD);while (:IsWindow(hWndPrevious)/ 检查窗口是否有预设的标记?/ 有,则是我们寻找的主窗if (:GetProp(hWndPrevious, m_pszExeName)/ 主窗口已最小化,则恢复其大小if (:IsIconic(hWnd
21、Previous):ShowWindow(hWndPrevious, SW_RESTORE);/ 将主窗激活:SetForegroundWindow(hWndPrevious);/ 将主窗的对话框激活:SetForegroundWindow(:GetLastActivePopup(hWndPrevious);/ 退出本实例return FALSE;/ 继续寻找下一个窗口hWndPrevious = :GetWindow(hWndPrevious, GW_HWNDNEXT);/ 前一实例已存在,但找不到其主窗/ 可能出错了/ 退出本实例return FALSE;AfxEnableControlC
22、ontainer();/ Standard initialization/ If you are not using these features and wish to reduce the size/ of your final executable, you should remove from the following/ the specific initialization routines you do not need.#ifdef _AFXDLLEnable3dControls(); / Call this when using MFC in a shared DLL#els
23、eEnable3dControlsStatic();/ Call this when linking to MFC statically#endifCEllipseWndDlg dlg;m_pMainWnd = int nResponse = dlg.DoModal();if (nResponse = IDOK)/ TODO: Place code here to handle when the dialog is/ dismissed with OKelse if (nResponse = IDCANCEL)/ TODO: Place code here to handle when the
24、 dialog is/ dismissed with Cancel/ Since the dialog has been closed, return FALSE so that we exit the/ application, rather than start the applications message pump.return FALSE;int CEllipseWndDlg:OnCreate(LPCREATESTRUCT lpCreateStruct) if (CDialog:OnCreate(lpCreateStruct) = -1)return -1;/ 设置寻找标记:Set
25、Prop(m_hWnd, AfxGetApp()-m_pszExeName, (HANDLE)1);return 0;void CEllipseWndDlg:OnDestroy() CDialog:OnDestroy();/ 删除寻找标记:RemoveProp(m_hWnd, AfxGetApp()-m_pszExeName); 6. 使应用程序显示为任务条通知区中的图标在 Windows 95 任务条的右边有一个区域被称为通知区域,在其中可以显示一些应用程序的图标,用鼠标单击其中的图标一般能弹出应用程序的菜单,双击则能显示应用程序的完整窗口界面。时钟和音量控制是任务条通知区最常见的图标。任务
26、条通知区编程可以通过 Windows 95 外壳编程接口函数 Shell_NotifyIcon来实现,该函数在 shellapi.h 头文件中声明,其原型如下:WINSHELLAPI BOOL WINAPI Shell_NotifyIcon( DWORD dwMessage, PNOTIFYICONDATA pnid);dwMessage 是对通知区图标进行操作的消息,主要有三中,如下表所示。Shell_NotifyIcon 使用的消息消息说明NIM_ADD在任务条通知区插入一个图标NIM_ DELETE在任务条通知区删除一个图标NIM_ MODIFY对任务条通知区的图标进行修改pnid 传入
27、一个 NOTIFYICONDATA 结构的指针。NOTIFYICONDATA 结构声明及各域的意义表示如下:typedef struct _NOTIFYICONDATA / nid DWORD cbSize; / NOTIFYICONDATA 结构的字节数HWND hWnd; / 处理通知区图标消息的窗口句柄UINT uID; / 通知区图标的 IDUINT uFlags; / 表示下述三项是否有意义的标志UINT uCallbackMessage; / 鼠标点击图标所发出消息的 IDHICON hIcon; / 图标句柄char szTip64; / 当鼠标移到图标上时显示的提示信息 NOT
28、IFYICONDATA, *PNOTIFYICONDATA;当用 Shell_NotifyIcon 在任务条通知区中放置一个图标时,同时也定义了一条回调消息,当用户用鼠标单击或双击图标时,NOTIFYICONDATA 结构中指定的窗口句柄将接受到该消息。该消息的 lParam 参数将说明鼠标操作的方式。当应用程序退出时,应删除任务条中的图标。下面的示例将说明如何使前述的椭圆窗口程序作为图标显示在任务条通知区中,当鼠标单击图标时,将弹出一个菜单,当双击时,椭圆窗口将完整显示。1. 用资源编辑器在 EllipseWnd 项目的 IDR_MENU_CONTEXT 菜单中增加一个菜单项“在任务条中插入
29、图标”(ID 为 IDM_INSERTICON)。2. 用资源编辑器在 EllipseWnd 项目中增加一个菜单资源 IDR_MENU_ICON ,在其中设定三个菜单项:“激活椭圆窗口”(ID 为 IDM_ACTIVEWINDOW)“关于.”(ID 为 IDM_ABOUTBOX)“退出 Alt+F4”(ID 为 IDM_EXIT)3. 在 CEllipseWndDlg.h 源文件中定义一个消息 UM_ICONNOTIFY 用以响应图标操作,并在 CEllipseWndDlg 类定义中增加响应该消息的处理函数 OnIconNotify。用ClassWizard 增加响应菜单命令 IDM_INSERTICON 和 IDM_ACTIVEWINDOW 的函数定义和模板。CEllipseWndDlg.h 中的修改如下:/ 定义响应图标操作的消息#define UM_ICONNOTIFY WM_USER+100class CEllipseWndDlg : public CDialog/ Constructionpublic:CEllipseWndDlg(CWnd* pParent = NULL); / standard constructor/ Dialog Data/AFX_DATA(CEllipseWndDlg)enum IDD = IDD_ELLIPSEWND_DIALOG ;