2009-05-27 14 views
6

Voglio adattarsi alle stringhe in una larghezza specifica. Esempio "Hello world" -> "... world", "Hello ...", "He ... rld".

Sai dove posso trovare il codice per questo? È un trucco accurato, molto utile per rappresentare le informazioni, e mi piacerebbe aggiungerlo nelle mie applicazioni (ovviamente).

Modifica: Mi dispiace, ho dimenticato di menzionare il Font parte. Non solo per stringhe di larghezza fissa ma in base al carattere.Come posso trasformare una stringa in una forma abbreviata?

+0

Stai utilizzando MFC o ...? – Smashery

risposta

8

Si tratta di una bella semplice algoritmo per scrivere se non si può trovare da nessuna parte - lo pseudocodice sarebbe qualcosa di simile:

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

o se volete i puntini di sospensione all'inizio della stringa, che seconda linea sarebbe:

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

o se si vuole che nel mezzo:

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

Modifica:
Suppongo che tu stia utilizzando MFC. Dal momento che lo desideri con i font, puoi utilizzare la funzione CDC::GetOutputTextExtent. Prova:

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

Se questo è troppo grande, allora si può poi fare una ricerca per cercare di trovare la stringa più lunga che si può andare bene; e potrebbe essere una ricerca intelligente quanto vuoi - ad esempio, potresti provare "Hello Worl ..." e poi "Hello Wor ..." e poi "Hello Wo ..."; rimuovendo un personaggio finché non lo trovi adatto. In alternativa, puoi fare un binary search - prova "Hello Worl ..." - se non funziona, usa solo metà dei caratteri del testo originale: "Ciao ..." - se ciò si adatta, prova a metà strada tra e: "Ciao Wo ..." fino a trovare il più lungo che si adatta ancora. Oppure si potrebbe provare alcuni euristica stima (dividere la lunghezza totale per la lunghezza desiderata, in proporzione stimare il numero di caratteri, e la ricerca da lì

La soluzione più semplice è qualcosa di simile:.

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ì, il suo MFC e io implementeremo un adattamento * intelligente *. Volevo vedere se c'è un'implementazione efficiente per quel tipo di cose prima di provare a implementare la mia. Ma immagino di dover sedermi ed essere creativo :) –

+0

Risposta fantastica, completa, utile. –

+0

Grazie, Aidan :-) – Smashery

2

È davvero piuttosto banale; Non penso che troverai codice specifico a meno che tu non abbia qualcosa di più strutturato in mente.

Che, fondamentalmente, vogliono:

  1. per ottenere la lunghezza della stringa che hai, e la larghezza della finestra.
  2. calcola quanti caratteri puoi ricavare dalla stringa originale, che in pratica sarà la larghezza della finestra-3. Chiama che k.
  3. A seconda che si desideri posizionare i puntini di sospensione al centro o all'estremità destra, prendere i caratteri del primo piano (k/2) da un'estremità, concatenati con "...", quindi concatenati con l'ultimo floor (k/2) caratteri (con possibilmente un altro carattere necessario a causa della divisione); o prendi i primi k caratteri, seguiti da "...".
2

penso La risposta di Smashery è un buon inizio.Un modo per arrivare al risultato finale sarebbe scrivere un codice di test con alcuni input di test e output desiderati. Una volta che hai un buon set di setup di test, puoi implementare il tuo codice di manipolazione delle stringhe fino a quando non passi tutti i tuoi test.

1
  • calcolare la larghezza del testo ( sulla base del tipo di carattere)

Nel caso in cui si utilizza MFC l'API GetOutputTextExtent ti porterà il valore.

  • se la larghezza supera il dato larghezza specifica, calcolare la larghezza dell'ellisse primo:

    ellipseWidth = calcolare la larghezza (...)

  • Rimuovere la parte stringa con larghezza ellipseWidth dalla fine e aggiungere ellisse.

    qualcosa di simile: Ciao ...

+0

Probabilmente troverai che larghezza (testo) + larghezza (ellissi) non è uguale alla larghezza (testo + puntini di sospensione). Questo perché alcuni glifi si sovrappongono quando vengono renderizzati per renderlo più bello. Quindi non puoi semplicemente sottrarre la larghezza ellittica dalla larghezza desiderata - devi controllare l'intera lunghezza (tra cui ellissi) – Smashery

+0

Sì, è corretto. In un ciclo dobbiamo verificare se la dimensione con l'ellisse corrisponde alla dimensione data (per identificare i caratteri da rimuovere dalla stringa). –

1

Per coloro che sono interessati per una routine completa, questa è la mia risposta :

/** 
* 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 Bel lavoro! Solo una parola di cautela: ho scoperto quando si faceva una cosa simile in un'altra lingua che quando si combinano le lettere in un font, potrebbero non essere necessariamente uguali alla somma delle singole lunghezze. Ad esempio, quando si posiziona "W" accanto a "A", il renderer dei caratteri potrebbe avvicinarli un po 'per renderlo più bello all'occhio umano. Quindi larghezza ('W') + larghezza ('A') potrebbe non essere uguale alla larghezza ('WA'). Per le stringhe piccole, probabilmente farà la differenza solo di alcuni pixel, ma per quelli più lunghi, potrebbe essere abbastanza evidente. – Smashery

+0

Grazie. Ok, lo controllerò. –

+0

Anche grazie per suggerire la ricerca binaria nella tua risposta, ottima idea! Forse lo implementerò quando avrò tempo - la ricerca binaria può essere complicata quindi l'ho saltata nella prima versione :) –

1

Se si desidera solo la "standard" formato ellissi, ("Ciao ...") è possibile utilizzare la funzione DrawText (o equivalient MFC function) che passa DT_END_ELLIPSIS.

Controllare anche DT_WORD_ELLIPSIS (tronca qualsiasi parola che non rientra nel rettangolo e aggiunge ellissi) o DT_PATH_ELLIPSIS (quale Explorer esegue per visualizzare percorsi lunghi).

+0

Penso di aver bisogno di un algoritmo quando ho postato la domanda, ma grazie comunque, è super conveniente! –

+0

Ho trovato la tua domanda cercando lo stesso problema, ho cercato su MSDN GetTextExtent e ho trovato questa semplice opzione in DrawText. Dal momento che avevo bisogno di puntini di sospensione alla fine, per me era abbastanza, e forse per alcuni futuri lettori sarà anche abbastanza! –

+0

sì, questo è il modo di farlo se usiamo GDI e abbiamo bisogno di un ritocco di testo di base. –

Problemi correlati