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

VISUAL C++编程的若干技巧——单实例;窗口风格等

2013-10-21 09:52 375 查看
Visual C++是一种面向对象的可视化编程工具,它提供的AppWizard能自动生成应用程序的标准框架,大大减轻了编程的工作量。本文主要介绍如下的编程技巧:修改主窗口风格、创建不规则形状窗口、用鼠标单击窗口标题条以外区域移动窗口、使用上下文菜单、使应用程序只能运行一个实例、使应用程序显示为任务条通知区中的图标和显示旋转文本等。

1. 修改主窗口风格

AppWizard生成的应用程序框架的主窗口具有缺省的窗口风格,比如在窗口标题条中自动添加文档名、窗口是叠加型的、可改变窗口大小等。要修改窗口的缺省风格,需要重载CWnd::PreCreateWindow(CREATESTRUCT& cs)函数,并在其中修改CREATESTRUCT型参数cs。

CWnd::PreCreateWindow 函数先于窗口创建函数执行。如果该函数被重载,则窗口创建函数将使用CWnd::PreCreateWindow 函数返回的CREATESTRUCT cs参数所定义的窗口风格来创建窗口;否则使用预定义的窗口风格。

CREATESTRUCT结构定义了创建函数创建窗口所用的初始参数,其定义如下:

typedef struct tagCREATESTRUCT {

LPVOID lpCreateParams; // 创建窗口的基本参数

HANDLE hInstance; // 拥有将创建的窗口的模块实例句柄

HMENU hMenu; // 新窗口的菜单句柄

HWND hwndParent; // 新窗口的父窗口句柄

int cy; // 新窗口的高度

int cx; // 新窗口的宽度

int y; // 新窗口的左上角Y坐标

int x; // 新窗口的左上角X坐标

LONG style; // 新窗口的风格

LPCSTR lpszName; // 新窗口的名称

LPCSTR lpszClass; // 新窗口的窗口类名

DWORD dwExStyle; // 新窗口的扩展参数

} CREATESTRUCT;

CREATESTRUCT结构的style域定义了窗口的风格。比如,缺省的MDI主窗口的风格中就包括FWS_ADDTOTITLE(在标题条中显示当前的工作文档名)、FWS_PREFIXTITLE(把文档名放在程序标题的前面)、WS_THICKFRAME(窗口具有可缩放的边框)等风格。由于多种风格参数由逻辑或(“|”)组合在一起的,因此添加某种风格,就只需用“|”把对应的参数加到CREATESTRUCT结构的style域中;删除已有的风格,则需用“&”连接CREATESTRUCT结构的style域与该风格的逻辑非值。

CREATESTRUCT结构的x、y、cx、cy域分别定义了窗口的初始位置和大小,因此,在CWnd::PreCreateWindow 函数中给它们赋值,将能定义窗口的初始显示位置和大小。

下例中的代码将主框窗口的大小将固定为1/4屏幕,标题条中仅显示窗口名,不显示文档名。

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

// 修改主窗风格

cs.style &= ~FWS_ADDTOTITLE; //去除标题条中的文档名

cs.style &= ~WS_THICKFRAME; //去除可改变大小的边框

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.cy = cxScreen/2; // 主窗高为1/2屏幕高

return CMDIFrameWnd::PreCreateWindow(cs);

}

2. 创建不规则形状窗口

标准的Windows窗口是矩形的,但在有些时候我们需要非矩形的窗口,比如圆形的、甚至是不规则的。借助CWnd类的SetWindowRgn函数可以创建不规则形状窗口。

CWnd::SetWindowRgn的函数原型如下:

int SetWindowRgn( HRGN hRgn, // 窗口区域句柄

BOOL bRedraw ); // 是否重画窗口

CRgn类封装了关于区域的数据和操作。通过(HRGN)强制操作可以从CRgn类中取得其HRGN值。

CRgn提供了CreateRectRgn、CreateEllipticRgn和CreatePolygonRgn成员函数,分别用以创建矩形、(椭)圆形和多边形区域。

创建非矩形窗口的方法如下:首先,在窗口类中定义区域类成员数据(如CRgn m_rgnWnd);其次,在窗口的OnCreate函数或对话框的OnInitDialog函数中调用CRgn类的CreateRectRgn、CreateEllipticRgn或CreatePolygonRgn函数创建所需的区域,并调用SetWindowRgn函数。

下例将生成一个椭圆窗口。

1. 在Developer Studio中选取File菜单中的New命令,在出现的New对话框中选择创建MFC AppWizard(exe)框架应用程序,并输入项目名为EllipseWnd。设定应用程序类型为基于对话框(Dialog based),其它选项按缺省值创建项目源文件。

2. 使用资源编辑器从主对话框(ID为IDD_ELLIPSEWND_DIALOG)删除其中的所有控制,并从其属性对话框(Dialog Properties)中设定其风格为Popup、无标题条和边框。

3. 在EllipseWndDlg.h源文件中给主对话框类CEllipseWndDlg增加一个CRgn类保护型数据成员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 & 0xFFF0) == IDM_ABOUTBOX);

ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);

if (pSysMenu != NULL)

{

CString strAboutMenu;

strAboutMenu.LoadString(IDS_ABOUTBOX);

if (!strAboutMenu.IsEmpty())

{

pSysMenu->AppendMenu(MF_SEPARATOR);

pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX,

strAboutMenu);

}

}

// Set the icon for this dialog. The framework does this automatically

// when the application's main window is not a dialog

SetIcon(m_hIcon, TRUE); // Set big icon

SetIcon(m_hIcon, FALSE); // Set small icon

// 设置窗口标题为“椭圆窗口”,虽然对话框没有标题条,

// 但在任务条的按钮中仍需要标题

SetWindowText(_T("椭圆窗口"));

// 取得屏幕宽、高

int cxScreen = ::GetSystemMetrics(SM_CXSCREEN);

int cyScreen = ::GetSystemMetrics(SM_CYSCREEN);

// 设置椭圆X、Y方向的半径

int nEllipseWidth = cxScreen/8;

int nEllipseHeight = cyScreen/8;

// 将窗口大小设为宽nEllipseWidth,高nEllipseHeight

// 并移至左上角

MoveWindow(0, 0, nEllipseWidth, nEllipseHeight);

// 创建椭圆区域m_rgnWnd

m_rgnWnd.CreateEllipticRgn(0, 0, nEllipseWidth, nEllipseHeight);

// 将m_rgnWnd设置为窗口区域

SetWindowRgn((HRGN)m_rgnWnd, TRUE);

return TRUE; // return TRUE unless you set the focus to a control

}

3. 用鼠标单击窗口标题条以外区域移动窗口

移动标准窗口是通过用鼠标单击窗口标题条来实现的,但对于没有标题条的窗口,就需要用鼠标单击窗口标题条以外区域来移动窗口。有两种方法可以达到这一目标。

方法一:当窗口确定鼠标位置时,Windows向窗口发送WM_NCHITTEST消息,可以处理该消息,使得只要鼠标在窗口内,Windows便认为鼠标在标题条上。这需要重载CWnd类处理WM_NCHITTEST消息的OnNcHitTest函数,在函数中调用父类的该函数,如果返回HTCLIENT,说明鼠标在窗口客户区内,使重载函数返回HTCAPTION,使Windows误认为鼠标处于标题条上。

下例是使用该方法的实际代码:

UINT CEllipseWndDlg::OnNcHitTest(CPoint point)

{

// 取得鼠标所在的窗口区域

UINT nHitTest = CDialog::OnNcHitTest(point);

// 如果鼠标在窗口客户区,则返回标题条代号给Windows

// 使Windows按鼠标在标题条上类进行处理,即可单击移动窗口

return (nHitTest==HTCLIENT) ? HTCAPTION : nHitTest;

}

方法二:当用户在窗口客户区按下鼠标左键时,使Windows认为鼠标是在标题条上,即在处理WM_LBUTTONDOWN消息的处理函数OnLButtonDown中发送一个wParam参数为HTCAPTION,lParam为当前坐标的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应用程序支持单击鼠标右键弹出上下文菜单的功能,这可通过处理WM_CONTEXTMENU消息来实现。

当在窗口内单击鼠标右键时,窗口将接收到WM_CONTEXTMENU消息,在该消息的处理函数内装载上下文菜单,并调用CMenu::TrackPopupMenu函数便可显示上下文菜单。CMenu::TrackPopupMenu函数的原型如下:

BOOL TrackPopupMenu( UINT nFlags, // 显示和选取方式标志

int x, int y, // 显示菜单的左上角坐标

CWnd* pWnd, // 接收菜单操作的窗口对象

LPCRECT lpRect = NULL ); // 敏感区域

为了使用上下文菜单,首先应在资源编辑器中编制好上下文菜单,假设上下文菜单名为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_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON,

point.x, point.y, this);

}

5. 使应用程序只能运行一个实例

Windows是多进程操作系统,框架生成的应用程序可以多次运行,形成多个运行实例。但在有些情况下为保证应用程序的安全运行,要求程序只能运行一个实例,比如程序要使用只能被一个进程单独使用的特殊硬件(例如调制解调器)时,必须限制程序只运行一个实例。

这里涉及两个基本的问题,一是在程序的第二个实例启动时,如何发现该程序已有一个实例在运行,而是如何将第一个实例激活,而第二个实例退出。

对于第一个问题,可以通过给应用程序设置信号量,实例启动时首先检测该信号量,如已存在,则说明程序已运行一个实例。

第二个问题的难点是获取第一个实例的主窗对象指针或句柄,然后便可用SetForegroundWindow来激活。虽然FindWindow函数能寻找正运行着的窗口,但该函数要求指明所寻找窗口的标题或窗口类名,不是实现通用方法的途径。我们可以用Win 32 SDK函数SetProp来给应用程序主窗设置一个特有的标记。用GetDesktopWindow可以获取Windows系统主控窗口对象指针或句柄,所有应用程序主窗都可看成该窗口的子窗口,即可用GetWindow函数来获得它们的对象指针或句柄。用Win
32 SDK函数GetProp查找每一应用程序主窗是否包含有我们设置的特定标记便可确定它是否我们要寻找的第一个实例主窗。使第二个实例退出很简单,只要让其应用程序对象的InitInstance函数返回FALSE即可。此外,当主窗口退出时,应用RemoveProp函数删除我们为其设置的标记。

下面的InitInstance、OnCreate和OnDestroy函数代码将实现上述的操作:

BOOL CEllipseWndApp::InitInstance()

{

// 用应用程序名创建信号量

HANDLE hSem = CreateSemaphore(NULL, 1, 1, m_pszExeName);

// 信号量已存在?

// 信号量存在,则程序已有一个实例运行

if (GetLastError() == ERROR_ALREADY_EXISTS)

{

// 关闭信号量句柄

CloseHandle(hSem);

// 寻找先前实例的主窗口

HWND hWndPrevious = ::GetWindow(::GetDesktopWindow(),

GW_CHILD);

while (::IsWindow(hWndPrevious))

{

// 检查窗口是否有预设的标记?

// 有,则是我们寻找的主窗

if (::GetProp(hWndPrevious, m_pszExeName))

{

// 主窗口已最小化,则恢复其大小

if (::IsIconic(hWndPrevious))

::ShowWindow(hWndPrevious,

SW_RESTORE);

// 将主窗激活

::SetForegroundWindow(hWndPrevious);

// 将主窗的对话框激活

::SetForegroundWindow(

::GetLastActivePopup(hWndPrevious));

// 退出本实例

return FALSE;

}

// 继续寻找下一个窗口

hWndPrevious = ::GetWindow(hWndPrevious,

GW_HWNDNEXT);

}

// 前一实例已存在,但找不到其主窗

// 可能出错了

// 退出本实例

return FALSE;

}

AfxEnableControlContainer();

// 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 _AFXDLL

Enable3dControls(); // Call this when using MFC in a shared DLL

#else

Enable3dControlsStatic();// Call this when linking to MFC statically

#endif

CEllipseWndDlg dlg;

m_pMainWnd = &dlg;

int nResponse = dlg.DoModal();

if (nResponse == IDOK)

{

// TODO: Place code here to handle when the dialog is

// dismissed with OK

}

else if (nResponse == IDCANCEL)

{

// TODO: Place code here to handle when the dialog is

// dismissed with Cancel

}

// Since the dialog has been closed, return FALSE so that we exit the

// application, rather than start the application's message pump.

return FALSE;

}

int CEllipseWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CDialog::OnCreate(lpCreateStruct) == -1)

return -1;

// 设置寻找标记

::SetProp(m_hWnd, AfxGetApp()->m_pszExeName, (HANDLE)1);

return 0;

}

void CEllipseWndDlg::OnDestroy()

{

CDialog::OnDestroy();

// 删除寻找标记

::RemoveProp(m_hWnd, AfxGetApp()->m_pszExeName);

}

6. 使应用程序显示为任务条通知区中的图标

在Windows 95任务条的右边有一个区域被称为通知区域,在其中可以显示一些应用程序的图标,用鼠标单击其中的图标一般能弹出应用程序的菜单,双击则能显示应用程序的完整窗口界面。时钟和音量控制是任务条通知区最常见的图标。

任务条通知区编程可以通过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传入一个NOTIFYICONDATA结构的指针。NOTIFYICONDATA结构声明及各域的意义表示如下:

typedef struct _NOTIFYICONDATA { // nid

DWORD cbSize; // NOTIFYICONDATA结构的字节数

HWND hWnd; // 处理通知区图标消息的窗口句柄

UINT uID; // 通知区图标的ID

UINT uFlags; // 表示下述三项是否有意义的标志

UINT uCallbackMessage; // 鼠标点击图标所发出消息的ID

HICON hIcon; // 图标句柄

char szTip[64]; // 当鼠标移到图标上时显示的提示信息

} NOTIFYICONDATA, *PNOTIFYICONDATA;

当用Shell_NotifyIcon在任务条通知区中放置一个图标时,同时也定义了一条回调消息,当用户用鼠标单击或双击图标时,NOTIFYICONDATA结构中指定的窗口句柄将接受到该消息。该消息的lParam参数将说明鼠标操作的方式。当应用程序退出时,应删除任务条中的图标。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: