我想将字符串放入特定的宽度。例如,“Hello world” -> “...world”、“Hello...”、“He...rld”。
你知道我在哪里可以找到代码吗?这是一个巧妙的技巧,对于表示信息非常有用,我想将它添加到我的应用程序中(当然)。
编辑:对不起,我忘了提到字体部分。不仅适用于固定宽度的字符串,还适用于字体。
6 回答
如果您在任何地方都找不到它,这是一个非常简单的算法,可以自己编写 - 伪代码类似于:
if theString.Length > desiredWidth:
theString = theString.Left(desiredWidth-3) + "...";
或者,如果您想在字符串的开头使用省略号,则第二行将是:
theString = "..." + theString.Right(desiredWidth-3);
或者如果你想要它在中间:
theString = theString.Left((desiredWidth-3)/2) + "..." + theString.Right((desiredWidth-3)/2 + ((desiredWidth-3) mod 2))
编辑:
我假设您使用的是 MFC。由于您需要字体,因此可以使用CDC::GetOutputTextExtent函数。尝试:
CString fullString
CSize size = pDC->GetOutputTextExtent(fullString);
bool isTooWide = size.cx > desiredWidth;
如果太大,那么您可以进行搜索以尝试找到可以容纳的最长字符串;它可以是你想要的聪明的搜索 - 例如,你可以尝试“Hello Worl ...”然后“Hello Wor ...”然后“Hello Wo ...”; 删除一个字符,直到找到合适为止。或者,您可以进行二分搜索- 尝试“Hello Worl ...” - 如果这不起作用,则只需使用原始文本的一半字符:“Hello ...” - 如果合适,请尝试介于两者之间它和:“Hello Wo...”,直到找到仍然合适的最长。或者您可以尝试一些估计启发式方法(将总长度除以所需长度,按比例估计所需字符数,然后从那里搜索。
简单的解决方案类似于:
unsigned int numberOfCharsToUse = fullString.GetLength();
bool isTooWide = true;
CString ellipsis = "...";
while (isTooWide)
{
numberOfCharsToUse--;
CString string = fullString.Left(numberOfCharsToUse) + ellipsis;
CSize size = pDC->GetOutputTextExtent(string);
isTooWide = size.cx > desiredWidth;
}
这真的很微不足道;我不认为你会找到特定的代码,除非你有更结构化的想法。
你基本上想要:
- 获取您拥有的字符串的长度和窗口宽度。
- 弄清楚你可以从原始字符串中取出多少个字符,这基本上是窗口宽度-3。称其为k。
- 根据您是将省略号放在中间还是在右手端,从一端取一楼(k / 2)个字符,与“...”连接,然后与最后一层(k /2) 字符(由于除法,可能还需要一个字符);或取前 k 个字符,后跟“...”。
我认为 Smashery 的回答是一个好的开始。获得最终结果的一种方法是编写一些带有一些测试输入和所需输出的测试代码。一旦你有一套好的测试设置,你就可以实现你的字符串操作代码,直到你通过所有的测试。
- 计算文本的宽度(基于字体)
如果您使用 MFC,API GetOutputTextExtent将为您提供值。
如果宽度超过给定的特定宽度,则首先计算椭圆宽度:
ellipseWidth = 计算 (...) 的宽度
从末尾删除宽度为 ellipseWidth 的字符串部分并附加椭圆。
比如:你好...
对于那些对完整例程感兴趣的人,这是我的答案:
/**
* Returns a string abbreviation
* example: "hello world" -> "...orld" or "hell..." or "he...rd" or "h...rld"
*
* style:
0: clip left
1: clip right
2: clip middle
3: pretty middle
*/
CString*
strabbr(
CDC* pdc,
const char* s,
const int area_width,
int style )
{
if ( !pdc || !s || !*s ) return new CString;
int len = strlen(s);
if ( pdc->GetTextExtent(s, len).cx <= area_width ) return new CString(s);
int dots_width = pdc->GetTextExtent("...", 3).cx;
if ( dots_width >= area_width ) return new CString;
// My algorithm uses 'left' and 'right' parts of the string, by turns.
int n = len;
int m = 1;
int n_width = 0;
int m_width = 0;
int tmpwidth;
// fromleft indicates where the clip is done so I can 'get' chars from the other part
bool fromleft = (style == 3 && n % 2 == 0)? false : (style > 0);
while ( TRUE ) {
if ( n_width + m_width + dots_width > area_width ) break;
if ( n <= m ) break; // keep my sanity check (WTF), it should never happen 'cause of the above line
// Here are extra 'swap turn' conditions
if ( style == 3 && (!(n & 1)) )
fromleft = (!fromleft);
else if ( style < 2 )
fromleft = (!fromleft); // (1)'disables' turn swapping for styles 0, 1
if ( fromleft ) {
pdc->GetCharWidth(*(s+n-1), *(s+n-1), &tmpwidth);
n_width += tmpwidth;
n--;
}
else {
pdc->GetCharWidth(*(s+m-1), *(s+m-1), &tmpwidth);
m_width += tmpwidth;
m++;
}
fromleft = (!fromleft); // (1)
}
if ( fromleft ) m--; else n++;
// Final steps
// 1. CString version
CString* abbr = new CString;
abbr->Format("%*.*s...%*.*s", m-1, m-1, s, len-n, len-n, s + n);
return abbr;
/* 2. char* version, if you dont want to use CString (efficiency), replace CString with char*,
new CString with _strdup("") and use this code for the final steps:
char* abbr = (char*)malloc(m + (len-n) + 3 +1);
strncpy(abbr, s, m-1);
strcpy(abbr + (m-1), "...");
strncpy(abbr+ (m-1) + 3, s + n, len-n);
abbr[(m-1) + (len-n) + 3] = 0;
return abbr;
*/
}