最近在做一个测试程序时,需要将程序的一些信息输出到Edit Control之中,以便于观察被测试程序的运行状态。在输出信息过程中发现,Edit Control默认是不会自动滚动,并将最新的信息显示到Edit Control中。
由于无法看到最新的显示信息,造成了我在第一时间没有办法看到程序的运行状态,所以我决定仿造VS的输出窗口,在输出信息时让滚动条自动滚动,以便我能够在第一时间看到程序的运行状态。
首先,为了使Edit Control能够接收多行文本,你必须使用代码或,者使用VS的界面设计器将Edit Control的Multline属性设置为true,为了使Edit Control更接近于VS的输出窗口,你还可以将Read Only以及Vertical Scroll属性设置为true。
下面就是实现自动滚动功能了,于是我打开MSDN,发现了EM_LINESCROLL消息能够让我们将Edit Control水平滚动多少字符或者垂直滚动多少行。当我兴奋的觉得问题解决了的时候,发现我还需要知道Edit Control中有多少行信息,才能让Edit Control滚动到新信息的出现位置。再次查询MSDN,发现EM_GETLINECOUNT可以获取到Edit Control中所包含字符的行数。让Edit Control滚动的具体的代码如下所示:
- ::SetDlgItemText(hwndDlg/*包含Edit Control主窗口的句柄*/, iEditItem/*Edit Control资源的编号*/, pszOutput/*要输出的信息*/);
- int iLine = ::SendMessage(hwndEdit/*Edit Control的句柄*/, EM_GETLINECOUNT, 0/*忽略*/, 0/*忽略*/);
- ::SendMessage(hwndEdit, EM_LINESCROLL, 0/*水平滚动的字符个数*/, iLine/*垂直滚动的行数*/);
现在Edit Control已经能够自动滚动了。不过等一等,如果你仔细的观察一下的话,会发现垂直滚动条会先跳跃到顶部,然后在跳跃到底部。这是由于在调用了SetDlgItemText后,垂直滚动条会首先按照默认的方式首先滚动到顶部,然后在向Edit Control发送了EM_LINESCROLL消息后,滚动条就会滚动到底部。为了使我们在观察输出信息时,不至于被不停跳动的滚动条闪花我们的双眼(囧)。我们需要在调用SetDlgItemText之前关闭Edit Control的重绘,然后在向Edit Control发送EM_LINESCROLL消息后,重新打开重绘。新的代码如下所示:
- ::SendMessage(hwndEdit, WM_SETREDRAW, FALSE/*关闭重绘*/, 0);
- ::SetDlgItemText(hwndDlg/*包含Edit Control主窗口的句柄*/, iEditItem/*Edit Control资源的编号*/, pszOutput/*要输出的信息*/);
- int iLine = ::SendMessage(hwndEdit/*Edit Control的句柄*/, EM_GETLINECOUNT, 0/*忽略*/, 0/*忽略*/);
- ::SendMessage(hwndEdit, EM_LINESCROLL, 0/*水平滚动的字符个数*/, iLine/*垂直滚动的行数*/);
- ::SendMessage(hwndEdit, WM_SETREDRAW, TRUE/*打开重绘*/, 0);
好了,现在滚动条不会在跳来跳去了。正当我准备搞定收工的时候,发现用上述代码实现的滚动功能,比起VS的输出窗口来说还是有一定的缺陷,那就是用上述代码虽然实现了滚动,但是Edit Control中的光标始终在第一行,而VS的输出窗口的光标会自动移动到最新输出信息的下一行。哎,所谓好事多磨吧,我只好再次的打开MSDN进行查找,这次我使用的是EM_SETSEL这个消息。当向Edit Control发送这个消息的时候,Edit Control会移动光标,选中Edit Control中的文本。所以只要我们所选择的开始字符和结束字符是同一个位置,光标就会移动到那个字符之后。而由于我们在每一行文本后面都加了"/r/n",所以光标就会移动到最新输出文本的下一行。修改后的代码如下:
- ::SendMessage(hwndEdit, WM_SETREDRAW, FALSE/*关闭重绘*/, 0);
- ::SetDlgItemText(hwndDlg/*包含Edit Control主窗口的句柄*/, iEditItem/*Edit Control资源的编号*/, pszOutput/*要输出的信息*/);
- int iLine = ::SendMessage(hwndEdit/*Edit Control的句柄*/, EM_GETLINECOUNT, 0/*忽略*/, 0/*忽略*/);
- ::SendMessage(hwndEdit, EM_LINESCROLL, 0/*水平滚动的字符个数*/, iLine/*垂直滚动的行数*/);
- int iOutputlen = _tcslen(pszOutput);
- ::SendMessage(hwndEdit, EM_SETSEL,iOutputLen/*要选中字符的起始位置*/, iOutputLen/*要选中字符的结束位置*/);
- ::SendMessage(hwndEdit, WM_SETREDRAW, TRUE/*打开重绘*/, 0);
为了方便大家使用,我结合上面所介绍的方法并加上了一些错误处理,写了一个函数OutputWithScroll函数,函数的代码如下:
- /// /fn BOOL OutputWithScroll(const HWND hwndDlg,
- /// const int iEditItem, const TCHAR *pszNewText,
- /// const int iOutputSize, TCHAR *pszOutput)
- ///
- /// /brief 将新信息添加到Edit Control的最后,并自动滚动到新信息。
- ///
- /// /param [in] hwndDlg Edit Control的父窗口句柄。
- /// /param [in] iEditItem Edit Control的资源编号。
- /// /param [in] pszNewText Edit 要添加到Edit Control中的新信息,如果新信息不是以/r/n结尾,函数会在输出信息
- /// 中自动添加。
- /// /param [in] iOutputSize 缓冲区的大小。该缓冲区用来获取Edit Control中已经存在的字符。
- /// /param [in] pszOutput 缓冲区指针。
- ///
- /// /return TRUE表示成功,FALSE表示失败。
- BOOL OutputWithScroll(const HWND hwndDlg,
- const int iEditItem, const TCHAR *pszNewText,
- const int iOutputSize, TCHAR *pszOutput)
- {
- if ((NULL == hwndDlg) || (NULL == pszNewText) ||
- (NULL == pszOutput))
- {
- return FALSE;
- }
- HWND hwndEdit = ::GetDlgItem(hwndDlg, iEditItem);
- if (NULL == hwndEdit)
- {
- return FALSE;
- }
- int iEditLen = 0;
- iEditLen = ::GetDlgItemText(hwndDlg, iEditItem, pszOutput, iOutputSize);
- int iNewTextLen = _tcslen(pszNewText);
- int iOutputLen = iEditLen + iNewTextLen;
- if (iOutputSize <= (iOutputLen + 2))
- {
- return FALSE;
- }
- _tcscat_s(pszOutput, iOutputSize, pszNewText);
- if ((0 == iNewTextLen) ||
- (('/r' != pszNewText[iNewTextLen - 2]) || ('/n' != pszNewText[iNewTextLen - 1])))
- {
- _tcscat_s(pszOutput, iOutputSize, _T("/r/n"));
- iOutputLen += 2;
- }
- ::SendMessage(hwndEdit, WM_SETREDRAW, FALSE, 0);
- if (!::SetDlgItemText(hwndDlg, iEditItem, pszOutput))
- {
- return FALSE;
- }
- int iLine = ::SendMessage(hwndEdit, EM_GETLINECOUNT,
- 0, 0);
- ::SendMessage(hwndEdit, EM_LINESCROLL,
- 0, iLine);
- ::SendMessage(hwndEdit, EM_SETSEL,
- iOutputLen, iOutputLen);
- ::SendMessage(hwndEdit, WM_SETREDRAW, TRUE, 0);
- return TRUE;
- }
上面的代码感觉实在太多了,下面再给大家一个使用MFC的版本,由于使用MFC的CEdit需要做的错误处理更少,所以代码要简洁得多:
- /// /fn void OutputWithScroll(const CString &strNewText,
- /// CEdit &edtOutput)
- ///
- /// /brief 将新信息添加到CEdit的最后,并自动滚动到新信息。
- ///
- /// /param [in] strNewText 要添加到CEdit中的新信息,如果新信息不是以/r/n结尾,函数会在输出信息
- /// 中自动添加。
- /// /param [in, out] edtOutput 要添加信息的CEdit。
- void OutputWithScroll(const CString &strNewText,
- CEdit &edtOutput)
- {
- CString strOutput;
- edtOutput.GetWindowText(strOutput);
- strOutput += strNewText;
- if ("/r/n" != strOutput.Right(2))
- {
- strOutput += "/r/n";
- }
- int iCount = strOutput.GetLength();
- edtOutput.SetRedraw(FALSE);
- edtOutput.SetWindowText(strOutput);
- int iLine = edtOutput.GetLineCount();
- edtOutput.LineScroll(iLine, 0);
- edtOutput.SetSel(iCount, iCount);
- edtOutput.SetRedraw(TRUE);
- }