📄 05.4 字符输入.txt
字号:
5.4 字符输入
下面要实现字符的输入功能,也就是当用户在键盘上按下某个字符按键后,要把该宇符输出到程序窗口上。这就需要程序捕获键盘按下这一消息。在第一章中曾介绍过 WM CHAR消息,这里,我们可以捕获这个消息,在该消息的响应函数中完成字符输出功能。但在字符输出时有一个问题需要注意,利用 TextOut函数在窗口中输出字符时,需要提供字符显示位置的 x坐标和 y坐标,例如,我们打算在 ( 0, 0 )位置处输出用户按键的字符,如果用户先后按下了 "a飞飞", 66c"这三个字符,对于 " a"字符,输出的位置是 ( 0,0 ) , 紧接着我们要在 "a"字符之后输出字符 "b"t但我们如何才能确定 "b"字符的输出位置呢?这在实现时有一定的难度,因为每个字符在屏幕上所占据的宽度都是不一样的,这样我们要获得下一个输入点的坐标就不太容易实现。为此,我们可以采取一种简单的方式,把每次输入的字符都存储到一个字符串中,例如,按下了" a"和 "b"字符键之后,将这两个字符组成一个字符串 :"ab飞当随后再按下 "c"键后,再把它与 "ab"组成一个字符串: "abc"。在程序中,每当按下新的字符时,都在窗口当前插入符的位置把这个字符串再重新输出一次。因为人眼具有视觉残留的效应,因此,用户感觉不到这种重新输出的变化,只能感觉到每按下一个字符时,窗口中就多了一个字符。
遵照这种思路,我们继续在己有的 Text程序中添加功能,首先让 CTextView类捕获 WM_CHAR消息,接着为该类定义一个 CString类型的成员变量 : m s位Line,专门用来存储输入的字符串,并在 CTextView类的构造函数中将这个变量初始化为空,初始化代码为:
这里仍有几个问题需要注意,第一个问题是,程序应该在当前插入符的位置输出字符。也就是说,程序运行时,如果用鼠标左键单击窗口中某个位置,那么插入符就应该移动到这个地方,随后输入的字符都应在此位置处往后输出。这样的话,我们还需要捕获鼠标左键按下消息 ( WM_ LBUTTONDOWN),在该消息响应函数中,把插入符移动到鼠标左键单击点处。这可以利用 CWnd类的 SetCaretPos函数来实现。该函数的声明形式为:
static void PASCAL SetCaretPos( POINT point };
从该函数的声明可以如道,它是一个静态函数,并带有一个 POINT结构体类型的参数,该参数表示一个点。本例中,这个点就是鼠标左键单击点。因此,我们就可以在鼠标左键按下这一消息的响应函数中添加例 5-11所示代码中加灰显示的那行代码,以完成插入符移动到当前鼠标左键单击点处的功能。
例5-11
void CTextView : :OnLButtonDown(UINT nFlags , CPoint point)
{ // TODO : Add your message handler code here and/ or call default
SetCaretPos(point);
CView : :OnLButtonDown(nFl ags , point);
Build井运行Text程序,然后用鼠标左键在程序窗口中任意位置处单击,将会发现插入符随着鼠标左键的单击而移动。程序结果如图5.17所示。
图 5.17插入符随鼠标左键单击而移动的效果
第二个需要注意的问题是,用来存储输入的字符串的成员变量: m strLine的取值变化问题。当用鼠标左键单击窗口中一个新的地方时,插入符就会移动到这个新位置,那么以后输入的字符都应从这个位置处开始输出,而以前输入的字符不应再从此位置处重新输出,因此,这时就要把m strLine中己有的内容清空。这可以利用 CString类的成员函数
Empty来实现。于是,我们在上述例 5-11所示代码中添加 Empty函数调用, 清空 m s位Line
中的内容,结果如例 5-12所示。
void CTextView : :OnLButtonDown(INT nFlags , CPoint point) {
// TODO: Add your message handler code here and/o工 call default
SetCaretPos(point);
m_strLine.Empty();
CView::OnLButtonDown(nFlags , point);
第三个问题是,因为每次输入的字符串都应在当前插入符位置,也就是鼠标左键单击点处开始显示。这样,就需要把鼠标左键单击点的坐标保存起来,以便在 OnChar函数中使用。于是我们为CTextView类再增加一个CPoint类型的成员变量来保存这个坐标值,将这个变量取名为: m_ptOrigin,井将其访问权限设置为私有的。然后在CTextView类的构
造函数中设置其初值为 0;接着在鼠标左键按下这一消息的响应函数中保存当前鼠标单击点,代码如例 5-13所示。
void CTextView: :OnLButtonDown(UINT nFlags , CPoint point )
// TODO : Add your rnessage handler code here and/ or c a ll default
SetCaretPos(point) ;
n_strLine . Enpty () ;
m_ptOrigin = point;
CView: :OnLBut tonDown(nFlags , p o int) ;
第四个问题是,在输出字符时,还应考虑到回车字符的处理。按下回车键后,插入符应换到下一行,随后的输入也应从这一新行开始输出。这样就需要请空上一行保存的字符,并计算插入符在下一行的新位置。这时插入符的横坐标不变,只有纵坐标发生了变化,而利用己保存的当前插入点的纵坐标加上当前字体的高度就可以得到回车后插入符的新位置的纵坐标。使用前面已经介绍过的 GetTextMetrics函数,即可获得当前设备描述表中字体的高度信息。
第五个问题是,在输出字符时,还要处理一个特殊的字符:退格键 ( ep Backspace键〉。当按下退格键后,应该删除屏幕上位于插入符前面的那个字符,也就是将这个字符从屏幕上抹掉,同时,插入符的位置应回退一个字符。这个问题的处理也有一定的困难。我们可以采用一种取巧的实现。我们知道删除一个字符,也就是让用户在屏幕上看不见这个字符。另外,如果文本的颜色与背景色一样的话,在屏幕上就看不到这个文本了,给用户的感觉就是删除了这个文本。因此,我们可以先把文本的颜色设置为背景色,在窗口中把该文本输出一次。然后从保存输入字符的字符串变量 ( ID_strLine )中把要删除的字符删除,再把文本的颜色设置为原来的颜色,之后再把字符串在窗口中输出一次。这时在屏幕上看到的就是正确的删除效果。因为这些操作都是连续的操作,而且执行的时间很短,给用户的感觉就是一按退格键,就删除了插入符前面的那个字符。
在具体实现时,为了获取背景色,可以利用 CDC类的 GetBkColor函数。而设置文本颜色,可以利用 CDC类提供的另一个成员函数 SetTextColor,该函数的声明如下所示 :
virtual COLORREF SetTextColor( COLORREF crColor );
这个函数将会返回文本先前的颜色。我们需要把这个返回值保存起来,因为后面还要把文本的颜色设置回先前的颜色再次显示。如果想要实现从字符串中删除一个字符,则可以利用 CString类的Left函数,该函数的声明形式如下所示 :
CString Left( int nCount ) const;
这个函数返回一个 CString对象,即返回指定字符串左边指定数目 ( nCount参数指定 )的字符。例如字符串的值为 "Windows",如果指定参数值为 3,调用 Left函数,那么将返
回字符串:"Win",即"Windows "左边的三个字符。因为本例要删除字符串最右边的那个字符,所以可以将Left函数的参数指定为待显示字符串中字符个数减去数值1后得到的数值即可。利用CString类提供的GetLength函数,可以得到指定字符串中字符的个数。
如果当前输入的字符不是以上这两种特殊字符(回车键和退格键),就应该把它添加到m strLine变量中,以便在屏幕上输出。
回车字符的ASCII码十六进制值是OxOd。退格键的 ASCII码十六进制值
解决了以上这些问题,就可以在WM CHAR消息响应函数中进行字符输出的处理了,具体实现代码如例5-14所示。
例5-14
1 . void CTextView : :OnChar(UINT nChar , UINT nRepCnt , UINT nF1ags)
2. {
3. /1 TODO : Add your message hand1er code here and/ or ca11 defau1t
4. CClientDC dc (this) ;
5. TEXTMETRI C tm ;
6. dc . GetTextMetrics (&tm) ;
7. if ( 0x0d == nChar)
8. {
9. m_strLine.Empty();
10. m-ptOrigin.y += tm.tmHeight ;
11. }
12. else if( Ox08 == nChar)
13. {
14. COLORREF clr = dC.SetTextColor(dc.GetBkColor());
15. dc.TextOut(m-ptOrigin.x,m-ptOrigin.y,m_strLine) ;
16. m_strLine = m_strLine.Left(m_strLine.GetLength() -1) ;
17. dc.SetTextColor(clr);
18. }
19. else
20. {
21.m_strLine+=nChar ;
22. }
23 . dc . TextOut(m-ptOrigin . x ,m-ptOrigin.y ,m_strLine);
24 . CView : :OnChar(nChar , nRepCnt , nF1ags) ;
25. }
提示:如果在编程时,不知道某个字符的ASCII值,可以在MSDN中以
"ASC."为索引进行查找,即可找到需要的内容。 Build并运行Text程序,将会发现程序现在能够在当前插入符位置处显示输入宇符了,
结果如图 5.18所示。
图 5.18字符输入程序运行结果
先到此
但是会发现这个程序仍有问题,当在屏幕上输出字符时,插入符的位置并没有改变。一般来说,插入符应该随着字符的输入而移动。我们已经知道可以利用 ' SetCaretPos函数来设置插入符的位置,但移动的位置如何确定呢?实际上,对于同一行上的输入来说,插入符横向移动的距离就是输入字符的宽度,而其纵坐标是不变的。根据前面的内容,我们知道利用 GetTextExtent函数就可以得到字符串的宽度。因此,在上述例子 14所示代码的第 23行之前添加例 5-15所示代码,以实现插入符随字符的输入而移动这一功能。
.tl5-15
CSize sz = dc .GetTextExtent(m_strLine);
CPoint pt ;
pt.x = m-ptOrigin.x + sZ .cx;
pt .y = m-ptOrigin.y;
SetCaretPos(pt) ;
再次 Build井运行 Text程序,并任意输入字符,可以发现程序屏幕上插入符随着字符的输入而移动了。 Text程序运行结果如图 5.19所示。这时,我们也可以试试回车键和退格键的效果,可以发现均成功实现所需功能。
女事毓程
图 5.19插入符随着输入的字符移动的效果
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -