修改之前的程序,运行后使用菜单命令,调用对话框输入显示的文本内容,在鼠标点击位置显示文本。
(1)在上例中,已经做好的对话框资源保留不变。在本例中只需要使用该对话框输入的文本内容,输入的坐标值不在本例中使用。
(2)为视图类添加鼠标消息WM_LBUTTONDOW和WM_LBUTTONUP的响应函数。
(3)编写鼠标消息的响应函数。
当用户在视图窗口中单击鼠标左键时,应用程序要捕获鼠标,记下鼠标当时的位置坐标,以便将文本串显示于该位置。
当用户松开鼠标左键时,应用程序要释放鼠标,并刷新视图,以显示程序运行结果。
记录鼠标位置的变量类型为CPoint类型,变量名为 m_TextPos,该变量设计为CApp15View类的成员,以便从该类的成员函数OnLButtonDown(UINT nFlags, CPoint point)的第二个参数point中取得鼠标当前位置信息。
打开App15View.h文件,添加代码如下。
class CApp15View : public CView
{ ……
protected:
CPoint m_TextPos; //记录鼠标左击时的位置坐标
// Generated message map functions
protected:
//{{AFX_MSG(CApp15View)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point); //自动生成
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
//自动生成
……
};
打开App15View.cpp文件,编写两个鼠标消息响应函数。
void CApp15View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
SetCapture(); //捕获鼠标
//将鼠标单击时的位置坐标保存到变量m_TextPos中
m_TextPos = point;
CView::OnLButtonDown(nFlags, point);
}
void CApp15View::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
ReleaseCapture(); //必须释放鼠标
CView::OnLButtonUp(nFlags, point);
RedrawWindow(); //刷新视图
}
为什么要捕获和释放鼠标呢?
由于用户的鼠标可以在屏幕上任意移动。当鼠标移出窗口外时,窗口无法收到鼠标消息。此时,如果松开了鼠标左键,应用程序由于无法接受到该条消息而不会调用松开鼠标消息响应函数,这样就造成了错误。
如何避免这种情况发生呢?解决的办法是要让窗口在鼠标移出窗口外时仍然能接受到鼠标消息,可以调用函数CWnd::SetCapture()来解决这一问题。
CWnd类的成员函数SetCapture()用于捕获鼠标,无论鼠标光标位置在何处,都会将所有后续的鼠标消息送给调用它的那个视图窗口。在用完后,需要用CWnd类的另一个成员函数ReleaseCapture()释放窗口对鼠标的控制,否则其他窗口将无法接收到鼠标消息。这一工作最好是在鼠标左键松开OnLButtonUp()时来做。
(4)修改相关文件中的代码。打开App15View.cpp文件,重新编写OnDraw()函数:
void CApp15View::OnDraw(CDC* pDC)
{
CApp15Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
pDC->TextOut(m_TextPos.x,m_TextPos.y,
pDoc->m_strText);
}
编译、链接和运行程序。
继续修改上例,增加界面修饰。包括:
(1)为菜单命令添加工具栏按钮;
(2)在状态栏中显示鼠标当前位置坐标,以及命令按钮的说明信息;
(3)随不同的命令状态改变鼠标光标形状;
(4)修改应用程序主窗口标题以及图标。
【编程步骤】
(2)修改工具栏按钮的ID。
注意:这里的ID: ID_SHOWTEXT与“文字|显示文字”菜单中的ID设为完全相同。
(3)进行“显示文字”按钮图标的编辑。如图所示。
(4)添加工具栏成员变量并修改OnCreate()函数。打开MainFrm.h,添加新的工具栏成员变量。
class CMainFrame : public CFrameWnd
{ ……
protected: // control bar embedded members
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
CToolBar m_wndMyToolBar ; //新的成员变量
……
}
(5)在MainFrm.cpp中为CMainFrame::OnCreate()函数添加如下代码,以便将自定义的工具栏IDR_MY_TOOLBAR与新添加的工具栏成员变量m_wndMyToolBar联系起来。同时设置新工具栏的停靠属性。
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{……
if (!m_wndMyToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndMyToolBar.LoadToolBar(IDR_MY_TOOLBAR))
{
TRACE0("未能创建工具栏\n");
return -1; // 未能创建
}
m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndMyToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_wndMenuBar);
DockPane(&m_wndToolBar);
DockPane(&m_wndMyToolBar);
return 0;
}
将工具栏资源IDR_MY_TOOLBAR与工具栏成员变量m_wndMyToolBar联系起来
状态栏在应用程序中的主要作用是起到提示作用。下面需要在状态栏中增加一个新的窗格,用来显示鼠标当前的位置坐标。
因为需要知道鼠标移动时的位置坐标,故需要在视图类的OnMouseMove()函数中添加相关代码。
(1)在状态栏资源中添加一个窗格。实现的方法是打开MainFrm.cpp文件,找到静态数组indicators的定义,在第一个数组元素ID_SEPARATOR后面增加一个新的数组元素,即添加了一个新的窗格,为了表明这个窗格的用途,故命名为ID_INDICATOR_MOUSE_POS。修改后的代码如下。
static UINT indicators[] =
{
ID_SEPARATOR, // 状态提示行窗格
ID_INDICATOR_MOUSE_POS, // 鼠标位置坐标值窗格
ID_INDICATOR_CAPS, // 大写
ID_INDICATOR_NUM, // 数字键
ID_INDICATOR_SCRL, // 滚动
};
(2) 在ResourceView的String Table中添加ID_INDICATOR_MOUSE_POS, 在标题中输入“鼠标的当前坐标”。
(3)添加鼠标移动消息WM_MOUSEMOVE的响应函数。
代码如下。
void CApp15View::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
//获得状态栏的指针
CMFCStatusBar *pStatus=(CMFCStatusBar*)(AfxGetApp()->m_pMainWnd->GetDescendantWindow(AFX_IDW_STATUS_BAR));
if(pStatus)
{
CString msg;
msg.Format(_T("(%4d, %4d)"), point.x, point.y);
//在状态栏的第二个窗格中输出当前鼠标位置
pStatus->SetPaneText(1, msg); //面板编号从0开始
}
CView::OnMouseMove(nFlags, point);
}
光标(cursor)是一种特殊的、可移动的32×32点阵图形,用来作为鼠标指针的图形标志。光标与图标的最大区别是光标有一个热点,用于确定光标当前的像素位置。
设置不同样式的光标是为了更加形象地表示出当前应用程序的状态,例如在执行“显示文字”命令时,改变鼠标光标的形状,用于表示当前的程序行为状态。
(1)创建新的自定义的光标资源。打开ResourceView标签,编辑“显示文字”光标,其ID为IDC_CURSOR_TEXT。设置其热点。
(2)在点击了“显示文字”菜单之后,光标形状就修改为IDC_CURSOR_TEXT。
在App15View.h中增加一个标志变量m_nCursor_Flag,初始化其值为0。当其值为1时表示当前处于“文本显示”命令状态。
class CApp15View : public CView
{ ……
protected: // create from serialization only
……
int m_nCursor_Flag;
//当其为1时,标识当前处于“显示文本”状态
……
}
(3)修改“显示文本”菜单命令的响应函数OnShowText ()。
void CApp15View::OnShowText()
{ // TODO: Add your command handler code here
CTextDialog tdlg; //定义一个对话框对象
if(tdlg.DoModal()==IDOK) //显示对话框
{
m_strText=tdlg.m_strText; //保存编辑框数据
m_nCursor_Flag = 1; //当前处于“显示文本”状态
}
RedrawWindow();
}
(3)改变光标形状的行为可在OnSetCursor函数中来实现。打开ClassWizard,class name选择CApp15View,即要改变鼠标光标的窗口对象(CWin的子类)是CApp15View,选择处理窗口的ON_WM_SETCURSOR消息,添加OnSetCursor函数。
程序运行时,只要鼠标进入到该窗口的区域,Windows就会给该窗口对象发送ON_WM_SETCURSOR消息,并触发OnSetCursor函数。这样就可以在OnSetCursor函数中增加控制鼠标光标的逻辑,如可以简单地显示自定义光标,也可以加入一些复杂的逻辑,比如在一定的区域内显示自定义光标,在其它区域显示标准光标。
编写OnSetCursor函数,代码如下。
BOOL CApp15View::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
// TODO: Add your message handler code here and/or call default
if(m_nCursor_Flag == 1)
::SetCursor(AfxGetApp()->LoadCursor(IDC_CURSOR_TEXT));
return TRUE;
//return CView::OnSetCursor(pWnd, nHitTest, message);
}
OnSetCursor()函数是CWnd类的成员函数,其原型如下。
fx_msg BOOL OnSetCursor( CWnd* pWnd, UINT nHitTest, UINT message );
如果鼠标输入没有被捕获并且鼠标使光标在CWnd对象内移动,则框架调用这个成员函数。
当用户将鼠标指针移到视图客户区时,鼠标不被捕获,Windows发送一个WM_ SETCURSOR消息到视图类。
它传递的一个参数是窗口句柄pWnd,即鼠标指针所指的窗口,这里指的是视图窗口本身;
OnSetCursor传递的第二个参数是nHitTest,这是一个鼠标点击测试代码,它以HTxxx开头,用于WM_NCHITTEST消息;
OnSetCursor传递的第三个参数是触发事件的鼠标消息的ID,例如WM_MOUSEMOVE。WM_ SETCURSOR是专门用来设置鼠标指针的消息,当设置了鼠标指针以后,应该让它返回TRUE以防止Windows再作缺省处理。
因此,本例中将调用父窗口的代码return CView::OnSetCursor(pWnd, nHitTest, message);注释掉,替换为return TRUE,这样才能够改变光标的形状。
SetCursor()函数是API函数,故而使用全局访问符::来引导。该函数的功能是确定光标的形状。
函数原型如下。
HCURSOR SetCursor(HCURSOR hCursor);
其参数hCursor为光标的句柄,该光标由LoadCursor函数载入。如果该参数为NULL,则该光标从屏幕上移开。仅当新光标与前面的光标不同时,才设置新光标,否则,该函数立即返回。其返回值:如果有前一个光标,则返回值是前光标的句柄;如果没有前光标,则返回值是NULL。
标题
在App的InitInstance()函数中添加:
m_pMainWnd->SetWindowTextW(_T(“我的文档”));
m_pMainWnd->ShowWindow(SW_SHOW);
窗口图标
在Windows中每个文件都有一个图标(icon)。应用程序的图标通常出现在程序标题栏的左上角、Windows桌面上、资源管理器的窗口中以及Windows底部的任务栏中。
一般情况下,应用程序采用MFC应用程序向导提供的默认图标。下面我们学习添加自己的图标。
(1)通过Insert|Resource命令插入Icon图标资源,使用图标资源编辑器编辑插入的图标IDI_MY_ICON。
(2)为了在标题栏中显示新创建的图标,在App15.cpp文件的InitInstance()函数中添加如下代码。
BOOL CApp15App::InitInstance()
{
HICON hIcon= AfxGetApp()->LoadIcon(IDI_MY_ICON);
m_pMainWnd->SetIcon(hIcon,TRUE);
m_pMainWnd->SetIcon(hIcon,FALSE);
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
编译连接运行程序 。
使用CWinApp类的成员函数LoadIcon()载入新创建的图标资源IDI_MY_ICON,LoadIcon()函数的原型如下。
HICON LoadIcon( LPCTSTR lpszResourceName ) const;
HICON LoadIcon( UINT nIDResource ) const;
其功能是载入可执行文件中以lpszResourceName为名或是nIDResource指定的图标资源。
调用CWnd类的成员函数SetIcon()将图标设定为其参数所标识的图标。该函数原型如下。
HICON SetIcon( HICON hIcon, BOOL bBigIcon );
返回值是指向一个图标的句柄。参数hIcon指要设置图标的句柄:参数bBigIcon如果为TRUE,则指定了32×32像素的图标;如果为FALSE,则指定了16×16像素的图标。
Exe图标
查看其中的debug目录发现所生成的应用程序App15的EXE文件仍使用默认图标。
若要修改EXE文件的图标,需要保证新替换图标的value值是最小的。因为exe文件图标是使用ResourceView标签页的Icon资源中value值最小的那个图标来做。打开Header Files下的文件Resource.h,可以观察到Icon资源下的所有图标的值,在本案例中如下所示。
#define IDR_MAINFRAME 128
#define IDR_MYTEXTTYPE 129
#define IDI_MY_ICON 134
更改EXE文件图标的具体步骤如下。
①将前面生成的icon1.ico文件复制一份,命名为icon2.ico,放置在res目录下;
②打开ResourceView标签页,在资源Icon上右击鼠标,在弹出的快捷菜单中选择“Import”命令将icon2.ico文件导入,系统自动为其生成的ID是IDI_ICON1;
③修改该图标的value值,把值改为Icon资源下几个图标中最小的:在图标的属性对话框中,在ID后面加上=value,比如IDI_ICON1=127。或者在Resource.h文件中加入下面的代码:
#define IDI_ICON1 127
重新连接并运行程序,再查看debug目录,可见EXE文件已使用替换的图标。