在实际的开发当中,我们经常会遇到要字符串比显示的区域要长的情况,从而导致显示不全。最合适的方法自然是跑马灯的滚动显示,不过这个可能稍微复杂了点,并不是所有的情况下都能适用;也许最最简易的,可能就是直接将超出范围的字符串用省略号替代了。
好像不是很难,不是么?但实际并非如此。比如,有一个字符串"你好,我们可以聊聊么?",你是决定只显示哪部分,而哪部分是被替代的呢?这个字符串所要显示的文字,如何确定?大部分菜鸟的第一反应,估计就是固定要只是显示多少个字符,超过的,就一律以省略号替代。实际上,这是不行的。如果字符全部为中文,或是全部为英文还行,如果又有中文和英文,那么由于互相字符宽度不同,显示就很死翘翘了。
假设我默认的显示文字为九个,那么在我的平台上显示的就是这样:
很明显看到,这两串字符的宽度完全是不同的,而下面的英文字符其实可以容纳更多。所以,以固定数目来做截取的标准,是完全不可取的。
有的朋友或许对GetTextMetrics比较熟悉,想到调用该函数返回字体的平均宽度,然后再与范围宽度相除。但毕竟平均不是实际,有时候用平均值来计算,误差也是很大的。
不过,微软毕竟还是微软,并没有将路给堵绝。它还给我们留了一个GetTextExtentPoint函数,用它来获取字符串所占的空间范围。
在具体说这个函数之前,我们先来看一副图:
这是字符串超出显示范围的一个情况。其中,蓝色方框的区域是显示的区域,绿色方框是应该显示的字符,而红色则是省略号占据的空间。对于我们来说,只需要知道绿色方框能包含多少个字符即可。
而GetTextExtentPoint函数能够计算输入的字符串占据的空间范围,所以通过它进行运算,就能获知我们需要显示多少个字符。现在的问题是,我们如何去调用这个函数?难道先从"你"开始,依次递进,以"你"、"你好"、"你好,"、"你好,这"等等这样的方式一个一个作为形参去进行测试?不用想,这效率,肯定奇差,甚至可能成为拖慢程序的一个禁锢。
所以,简单点,我们就用二分法吧。声明一个函数,它可以接收当前的hdc,显示范围的大小,以及测试的字符串,返回的是该显示范围能容纳下的字符。
故此,函数实现如下:
- DWORD GetComfortSize(HDC hdc,DWORD dwWidth,const TSTRING &strText)
- {
- //二分法查找
- DWORD dwComfortSize = 0;
- DWORD dwBeginSize = 0;
- DWORD dwEndSize = strText.size();
- while(TRUE)
- {
- DWORD dwMiddleSize = (dwEndSize + dwBeginSize) / 2;
- if(dwMiddleSize == dwBeginSize || dwMiddleSize == dwEndSize)
- {
- //两个点之间已经没有数值可以检测,退出循环
- dwComfortSize = dwBeginSize;
- break;
- }
- SIZE sizeChk = {0};
- ::GetTextExtentPoint(hdc,strText.c_str(),dwMiddleSize,&sizeChk);
- if(sizeChk.cx == dwWidth)
- {
- //数值刚好合适,跳出循环
- dwComfortSize = dwMiddleSize;
- break;
- }
- else if(static_cast<DWORD>(sizeChk.cx) > dwWidth)
- {
- //重新设置边界
- dwEndSize = dwMiddleSize;
- }
- else
- {
- //重新设置边界
- dwBeginSize = dwMiddleSize;
- }
- }
- return dwComfortSize;
- }
函数的调用其实也很简单,但我们要注意,调用前先将省略号的空间给去掉。故实际的调用代码简单如下:
- //获取省略号的占据范围
- static const TSTRING FLAG_ELLIPSIS = TEXT("");
- SIZE sizeEllipsis = {0};
- GetTextExtentPoint(hdc,FLAG_ELLIPSIS.c_str(),FLAG_ELLIPSIS.size(),&sizeEllipsis);
- DWORD dwMaxDisp = GetComfortSize(hdc,sizeExtent.cx - sizeEllipsis.cx,strChk);
那么,看看我们这函数的实际显示成果吧: