2009-05-27 9 views
6

Quiero encajar cadenas en un ancho específico. Ejemplo, "Hola mundo" -> "... mundo", "Hola ...", "Él ... rld".

¿Sabes dónde puedo encontrar el código para eso? Es un buen truco, muy útil para representar información, y me gustaría agregarlo en mis aplicaciones (por supuesto).

Editar: Lo siento, me olvidé de mencionar el Parte de la fuente. No solo para cadenas de ancho fijo sino de acuerdo con la cara de la fuente.¿Cómo puedo transformar una cadena en una forma abreviada?

+0

¿Está utilizando MFC, o ...? – Smashery

Respuesta

8

Es un algoritmo bastante simple para escribir usted mismo si usted no puede encontrar en cualquier lugar - el pseudocódigo sería algo así como:

if theString.Length > desiredWidth: 
    theString = theString.Left(desiredWidth-3) + "..."; 

o si desea que los puntos suspensivos al comienzo de la cadena, esa segunda línea sería:

theString = "..." + theString.Right(desiredWidth-3); 

o si desea que en el medio:

theString = theString.Left((desiredWidth-3)/2) + "..." + theString.Right((desiredWidth-3)/2 + ((desiredWidth-3) mod 2)) 

Editar:
Supongo que está utilizando MFC. Como lo quieres con fuentes, puedes usar la función CDC::GetOutputTextExtent. Proveedores:

CString fullString 
CSize size = pDC->GetOutputTextExtent(fullString); 
bool isTooWide = size.cx > desiredWidth; 

Si eso es demasiado grande, entonces usted puede entonces hacer una búsqueda para tratar de encontrar la cadena más larga que puede encajar; y podría ser una búsqueda tan inteligente como desee; por ejemplo, podría intentar con "Hola, Worl ..." y luego "Hola, Wor ..." y luego "Hola, Wo ..."; eliminando un personaje hasta que encuentre que encaja. Alternativamente, podría hacer un binary search - intente "Hola, Worl ..." - si eso no funciona, entonces simplemente use la mitad de los caracteres del texto original: "Hola ..." - si eso le sirve, intente a medio camino entre y: "Hola Wo ..." hasta que encuentres el más largo que todavía le queda. O usted podría intentar alguna heurística estimación (dividir la longitud total de la longitud deseada, proporcionalmente estimar el número necesario de caracteres, y la búsqueda de allí

La solución más sencilla es algo así como:.

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; 
} 
+0

Sí, es MFC y voy a implementar un ajuste * inteligente *. Quería ver si hay una implementación eficiente para ese tipo de cosas antes de intentar implementar la mía. Pero supongo que tengo que sentarme y ser creativo :) –

+0

Respuesta impresionante, completa y útil. –

+0

Gracias, Aidan :-) – Smashery

2

Es realmente bastante trivial; No creo que encuentres un código específico a menos que tengas algo más estructurado en mente.

Es, básicamente, quieren:

  1. para obtener la longitud de la cadena que tiene, y el ancho de la ventana.
  2. averigua cuántos charaters puedes tomar de la cadena original, que básicamente será el ancho de la ventana-3. Llame al k.
  3. Dependiendo de si desea poner las elipsis en el medio o en el extremo derecho, tome los caracteres del primer piso (k/2) de un extremo, concatenado con "...", y luego concatenado con el último suelo (k/2) caracteres (posiblemente con un personaje más debido a la división); o tome los primeros k caracteres, seguidos por "...".
2

pienso La respuesta de Smashery es un buen comienzo.Una forma de llegar al resultado final sería escribir algún código de prueba con algunas entradas de prueba y salidas deseadas. Una vez que tenga un buen conjunto de configuración de pruebas, puede implementar su código de manipulación de cadenas hasta que pase todas sus pruebas.

1
  • calcular el ancho del texto ( basado en el tipo de letra)

En caso de que utilice la API de MFC GetOutputTextExtent le dará el valor.

  • si la anchura excede el ancho específico dado, calcular la amplitud de la elipse primero:

    ellipseWidth = calcular la anchura de (...)

  • Retire la parte cadena con anchura ellipseWidth desde el final y anexar elipse.

    algo como: Hola ...

+0

Sin embargo, probablemente encontrará que el ancho (texto) + ancho (puntos suspensivos) no es igual al ancho (texto + puntos suspensivos). Esto se debe a que algunos glifos se superponen cuando se representan para que se vea más bonito. Por lo tanto, no puede simplemente restar el ancho de la elipse del ancho deseado; debe verificar la longitud completa (incluidas las elipsis) – Smashery

+0

Sí, eso es correcto. En un bucle, debemos verificar si el tamaño con elipse corresponde al tamaño dado (para identificar los caracteres a eliminar de la cadena). –

1

Para aquellos que estén interesados ​​para una rutina completa, esta es mi respuesta :

/** 
* 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; 
    */ 
} 
+0

+1 ¡Buen trabajo! Solo una palabra de advertencia: descubrí al hacer algo similar en otro idioma que cuando combinas letras en una fuente, puede que no necesariamente igualen la suma de sus longitudes individuales. Por ejemplo, cuando pones 'W' al lado de 'A', el renderizador de fuentes puede juntarlos un poco para que se vea más bonito para el ojo humano. Entonces, el ancho ('W') + ancho ('A') puede no ser igual al ancho ('WA'). Para cadenas pequeñas, probablemente solo hará la diferencia de unos pocos píxeles, pero para los más largos, puede ser bastante notorio. – Smashery

+0

Gracias. Ok, lo verificaré. –

+0

También gracias por sugerir la búsqueda binaria en su respuesta, ¡muy buena idea! Tal vez lo implemente cuando tenga tiempo, la búsqueda binaria puede ser complicada, así que me salté en la primera versión :) –

1

Si lo que desea es el "estándar" formato de elipsis, ("Hola ...") puede usar la función DrawText (o la función equivaliente MFC function) pasando DT_END_ELLIPSIS.

Consulte también DT_WORD_ELLIPSIS (trunca cualquier palabra que no quepa en el rectángulo y agrega puntos suspensivos) o DT_PATH_ELLIPSIS (lo que hace Explorer para mostrar las rutas largas).

+0

Creo que necesitaba un algoritmo cuando publiqué la pregunta, pero gracias de todos modos, ¡es súper conveniente! –

+0

Encontré su pregunta buscando el mismo problema, busqué en MSDN GetTextExtent y encontré esta sencilla opción en DrawText. Ya que necesitaba puntos suspensivos al final, para mí fue suficiente, ¡y tal vez para algunos futuros lectores también sea suficiente! –

+0

sí, esa es la manera de hacerlo si usamos GDI y necesitamos un recorte de texto básico. –

Cuestiones relacionadas