2009-05-27 5 views
6

Je veux ajuster chaînes dans une largeur spécifique. Exemple: "Bonjour tout le monde" -> "... monde", "Bonjour ...", "Il ... rld".

Savez-vous où je peux trouver le code pour cela? C'est une astuce, très utile pour représenter l'information, et j'aimerais l'ajouter dans mes applications (bien sûr).

Modifier: Désolé, j'ai oublié de mentionner le Police partie. Pas seulement pour les chaînes de largeur fixe, mais en fonction de la face de la police.Comment puis-je transformer une chaîne en une forme abrégée?

+0

Utilisez-vous MFC, ou ...? – Smashery

Répondre

8

Il est un algorithme assez simple pour vous écrire si vous ne pouvez pas le trouver nulle part - le pseudocode serait quelque chose comme:

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

ou si vous voulez les points de suspension au début de la chaîne, cette deuxième ligne serait:

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

ou si vous voulez au milieu:

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

Modifier:
Je suppose que vous utilisez MFC. Puisque vous le voulez avec des polices, vous pouvez utiliser la fonction CDC::GetOutputTextExtent. Essayez:

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

Si c'est trop grand, alors vous pouvez faire une recherche pour essayer de trouver la plus longue chaîne que vous pouvez adapter; et il pourrait être une recherche aussi intelligente que vous voulez - par exemple, vous pourriez simplement essayer "Bonjour Worl ..." et puis "Bonjour Wor ..." et puis "Bonjour Wo ..."; retirer un caractère jusqu'à ce que vous trouviez qu'il convient. Alternativement, vous pouvez faire un binary search - essayez "Hello Worl ..." - si cela ne fonctionne pas, alors utilisez simplement la moitié des caractères du texte original: "Bonjour ..." - si cela vous convient, essayez à mi-chemin entre et: "Bonjour Wo ..." jusqu'à ce que vous trouviez le plus long qui soit encore adapté. Ou vous pouvez essayer une heuristique d'estimation (diviser la longueur totale de la longueur désirée, estimer proportionnellement le nombre de caractères, et la recherche à partir de là

La solution simple est quelque chose comme:.

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

Oui, son MFC et moi allons mettre en place un ajustement * intelligent *. Je voulais voir s'il y a une mise en œuvre efficace pour ce genre de choses avant d'essayer de mettre en place la mienne. Mais je suppose que je dois m'asseoir et être créatif :) –

+0

Réponse géniale, complète, utile. –

+0

Merci, Aidan :-) – Smashery

2

C'est vraiment plutôt trivial; Je ne pense pas que vous trouverez du code spécifique à moins que vous ayez quelque chose de plus structuré à l'esprit.

Vous voulez essentiellement:

  1. pour obtenir la longueur de la chaîne que vous avez, et la largeur de la fenêtre.
  2. déterminez le nombre de charaters que vous pouvez prendre dans la chaîne d'origine, qui sera essentiellement la largeur de la fenêtre-3. Appelez cela k.
  3. Selon que vous souhaitez placer l'ellipse au milieu ou à l'extrémité droite, prenez les caractères du premier étage (k/2) à une extrémité, concaténés avec "...", puis concaténés avec le dernier les caractères d'étage (k/2) (avec éventuellement un caractère de plus nécessaire à cause de la division); ou prenez les premiers k caractères, suivis de "...".
2

Je pense La réponse de Smashery est un bon début.Une façon d'arriver au résultat final serait d'écrire un code de test avec quelques entrées de test et les sorties désirées. Une fois que vous avez configuré un bon ensemble de tests, vous pouvez implémenter votre code de manipulation de chaîne jusqu'à ce que tous vos tests soient réussis.

1
  • Calculer la largeur du texte ( basé sur la police)

Si vous utilisez l'API MFC GetOutputTextExtent vous obtiendrez la valeur.

  • si la largeur est supérieure à la donnée largeur spécifique, calculer la largeur de l'ellipse première:

    ellipseWidth = Calcul de la largeur de (...)

  • Supprimer la partie de chaîne avec ellipseWidth largeur à partir de la fin et ajouter l'ellipse.

    quelque chose comme: Bonjour ...

+0

Vous trouverez probablement, cependant, que largeur (texte) + largeur (points de suspension) n'est pas égale largeur (texte + points de suspension). C'est parce que certains glyphes se chevauchent quand ils sont rendus pour le rendre plus joli. Donc, vous ne pouvez pas simplement soustraire l'ellipsewidth de la largeur souhaitée - vous devez vérifier la longueur totale (y compris les points de suspension) – Smashery

+0

Oui, c'est correct. Dans une boucle, nous devons vérifier si la taille avec ellipse correspond à la taille donnée (afin d'identifier les caractères à supprimer de la chaîne). –

1

Pour ceux qui sont intéressés par une routine complète, voici ma réponse :

/** 
* 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 Beau travail! Juste un mot d'avertissement: j'ai découvert en faisant une chose similaire dans une autre langue que lorsque vous combinez des lettres dans une police, ils ne peuvent pas nécessairement égaler la somme de leurs longueurs individuelles. Par exemple, lorsque vous placez 'W' à côté de 'A', le rendu de polices peut les rapprocher un peu pour le rendre plus joli à l'œil humain. Donc largeur ('W') + largeur ('A') peut ne pas égaler la largeur ('WA'). Pour les petites chaînes, il ne fera probablement que la différence de quelques pixels, mais pour les plus longues, il peut être assez perceptible. – Smashery

+0

Merci. Ok, je vais vérifier. –

+0

Aussi merci de proposer la recherche binaire dans votre réponse, très bonne idée! Peut-être que je l'implémenterai quand j'aurai le temps - la recherche binaire peut être difficile donc je l'ai sauté dans la première version :) –

1

Si vous voulez juste le "standard" Au format ellipse, ("Bonjour ..."), vous pouvez utiliser la fonction DrawText (ou la fonction équivalente MFC function) en passant le DT_END_ELLIPSIS.

Découvrez également DT_WORD_ELLIPSIS (il tronque tout mot qui ne rentre pas dans le rectangle et ajoute des ellipses) ou DT_PATH_ELLIPSIS (ce que fait Explorer pour afficher les longs chemins).

+0

Je pense avoir besoin d'un algorithme quand j'ai posté la question, mais merci quand même, c'est super pratique! –

+0

J'ai trouvé votre question à la recherche du même problème, j'ai consulté MSDN GetTextExtent et trouvé cette option simple dans DrawText. Comme j'avais besoin d'ellipses à la fin, pour moi c'était suffisant, et peut-être que certains futurs lecteurs suffiront aussi! –

+0

oui, c'est la façon de le faire si nous utilisons GDI et que nous avons besoin d'une coupe de texte de base. –

Questions connexes