c# .net winforms label justify

c# - Cómo justificar texto en una etiqueta



.net winforms (3)

Desafortunadamente, solo se admiten los tres tipos de alineación más básicos y simples: Right , Left y Center .

El cuarto, Justified o Block , no es compatible con ningún control .NET afaik, ni siquiera en un RichtTextBox :-(

La única solución sería agregar espacios o mejor un espacio en blanco más pequeño como el thin space (U + 2009) o el hair space (U + 200A) entre las palabras, es decir, después de los espacios regulares hasta que cambie la Height la Label . Luego, retroceda uno e intente encontrar el siguiente punto de inserción, es decir, la siguiente línea, etc., hasta llegar al final del texto.

Un poco complicado pero no terriblemente duro.

Tengo una etiqueta que se muestra en más de una línea y me gustaría justificar el texto (alinear a la izquierda y a la derecha). ¿Cuál es la mejor manera de lograr eso?


Esta es una implementación de la solución propuesta por TaW. Es solo el código base para la implementación: sin reajustes automáticos, etc.

public void Justify(System.Windows.Forms.Label label) { string text = label.Text; string[] lines = text.Split(new[]{"/r/n"}, StringSplitOptions.None).Select(l => l.Trim()).ToArray(); List<string> result = new List<string>(); foreach (string line in lines) { result.Add(StretchToWidth(line, label)); } label.Text = string.Join("/r/n", result); } private string StretchToWidth(string text, Label label) { if (text.Length < 2) return text; // A hair space is the smallest possible non-visible character we can insert const char hairspace = ''/u200A''; // If we measure just the width of the space we might get too much because of added paddings so we have to do it a bit differently double basewidth = TextRenderer.MeasureText(text, label.Font).Width; double doublewidth = TextRenderer.MeasureText(text + text, label.Font).Width; double doublewidthplusspace = TextRenderer.MeasureText(text + hairspace + text, label.Font).Width; double spacewidth = doublewidthplusspace - doublewidth; //The space we have to fill up with spaces is whatever is left double leftoverspace = label.Width - basewidth; //Calculate the amount of spaces we need to insert int approximateInserts = Math.Max(0, (int)Math.Floor(leftoverspace / spacewidth)); //Insert spaces return InsertFillerChar(hairspace, text, approximateInserts); } private static string InsertFillerChar(char filler, string text, int inserts) { string result = ""; int inserted = 0; for (int i = 0; i < text.Length; i++) { //Add one character of the original text result += text[i]; //Only add spaces between characters, not at the end if (i >= text.Length - 1) continue; //Determine how many characters should have been inserted so far int shouldbeinserted = (int)(inserts * (i+1) / (text.Length - 1.0)); int insertnow = shouldbeinserted - inserted; for (int j = 0; j < insertnow; j++) result += filler; inserted += insertnow; } return result; }

En acción:


Otra implementación.
Este inserta "Espacios de cabello" entre palabras solamente.

EDITAR:
Se agregó un método que implementa el alineamiento de bloque de párrafo.
Tanto JustifyParagraph() como JustifyLine() llaman al método de trabajo Justify() .

EDIT2:
Llamada al método modificada.

label1.Text = JustifyParagraph(label1.Text, label1.Font, label1.ClientSize.Width); public string JustifyParagraph(string text, Font font, int ControlWidth) { string result = string.Empty; List<string> ParagraphsList = new List<string>(); ParagraphsList.AddRange(text.Split(new[] { "/r/n" }, StringSplitOptions.None).ToList()); foreach (string Paragraph in ParagraphsList) { string line = string.Empty; int ParagraphWidth = TextRenderer.MeasureText(Paragraph, font).Width; if (ParagraphWidth > ControlWidth) { //Get all paragraph words, add a normal space and calculate when their sum exceeds the constraints string[] Words = Paragraph.Split('' ''); line = Words[0] + (char)32; for (int x = 1; x < Words.Length; x++) { string tmpLine = line + (Words[x] + (char)32); if (TextRenderer.MeasureText(tmpLine, font).Width > ControlWidth) { //Max lenght reached. Justify the line and step back result += Justify(line.TrimEnd(), font, ControlWidth) + "/r/n"; line = string.Empty; --x; } else { //Some capacity still left line += (Words[x] + (char)32); } } //Adds the remainder if any if (line.Length > 0) result += line + "/r/n"; } else { result += Paragraph + "/r/n"; } } return result.TrimEnd(new[]{ ''/r'', ''/n'' }); }


JustifyLines() solo trata con líneas de texto individuales (más cortas que el área del cliente)

textBox1.Text = JustifyLines(textBox1.Text, textBox1.Font, textBox1.ClientSize.Width); public string JustifyLines(string text, Font font, int ControlWidth) { string result = string.Empty; List<string> Paragraphs = new List<string>(); Paragraphs.AddRange(text.Split(new[] { "/r/n" }, StringSplitOptions.None).ToList()); //Justify each paragraph and re-insert a linefeed foreach (string Paragraph in Paragraphs) { result += Justify(Paragraph, font, ControlWidth) + "/r/n"; } return result.TrimEnd(new[] {''/r'', ''/n''}); }




El método trabajador

private string Justify(string text, Font font, int width) { char SpaceChar = (char)0x200A; List<string> WordsList = text.Split((char)32).ToList(); if (WordsList.Capacity < 2) return text; int NumberOfWords = WordsList.Capacity - 1; int WordsWidth = TextRenderer.MeasureText(text.Replace(" ", ""), font).Width; int SpaceCharWidth = TextRenderer.MeasureText(WordsList[0] + SpaceChar, font).Width - TextRenderer.MeasureText(WordsList[0], font).Width; //Calculate the average spacing between each word minus the last one int AverageSpace = ((width - WordsWidth) / NumberOfWords) / SpaceCharWidth; float AdjustSpace = (width - (WordsWidth + (AverageSpace * NumberOfWords * SpaceCharWidth))); //Add spaces to all words return ((Func<string>)(() => { string Spaces = ""; string AdjustedWords = ""; for (int h = 0; h < AverageSpace; h++) Spaces += SpaceChar; foreach (string Word in WordsList) { AdjustedWords += Word + Spaces; //Adjust the spacing if there''s a reminder if (AdjustSpace > 0) { AdjustedWords += SpaceChar; AdjustSpace -= SpaceCharWidth; } } return AdjustedWords.TrimEnd(); }))(); }


Sobre el RichTextBox.
@TaW dice que no es compatible con Block Align, pero esto no es exactamente cierto.
RichTextBox se basa notoriamente en la clase RichEdit y esa clase admite "Justificación".
Esto se informa en el SDK de plataforma anterior (con ejemplos).
RichTextBox tiene su AdvancedTypographicsOption explícitamente truncada durante la creación del identificador.
(No se trata de implementar estructuras PARAFORMAT vs. PARAFORMAT2, eso es irrelevante, es deliberado).

Entonces esta es una "cura" para el pobre RichTextBox. Una clase que se deriva de ella y anula OnHandleCreated para volver a habilitar la "Justificación".
Funciona en un nivel de párrafo o desde un punto en adelante.

public class JustifiedRichTextBox : RichTextBox { [DllImport("user32", CharSet = CharSet.Auto)] private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In] [Out] ref PARAFORMAT2 pf); [DllImport("user32", CharSet = CharSet.Auto)] private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam); public enum TextAlignment { Left = 1, Right, Center, Justify } private const int EM_SETEVENTMASK = 1073; private const int EM_GETPARAFORMAT = 1085; private const int EM_SETPARAFORMAT = 1095; private const int EM_SETTYPOGRAPHYOPTIONS = 1226; private const int TO_ADVANCEDTYPOGRAPHY = 0x1; private const int WM_SETREDRAW = 11; private const int PFM_ALIGNMENT = 8; private const int SCF_SELECTION = 1; [StructLayout(LayoutKind.Sequential)] private struct PARAFORMAT2 { //---------------------------------------- public int cbSize; // PARAFORMAT public uint dwMask; public short wNumbering; public short wReserved; public int dxStartIndent; public int dxRightIndent; public int dxOffset; public short wAlignment; public short cTabCount; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public int[] rgxTabs; //---------------------------------------- public int dySpaceBefore; // PARAFORMAT2 public int dySpaceAfter; public int dyLineSpacing; public short sStyle; public byte bLineSpacingRule; public byte bOutlineLevel; public short wShadingWeight; public short wShadingStyle; public short wNumberingStart; public short wNumberingStyle; public short wNumberingTab; public short wBorderSpace; public short wBorderWidth; public short wBorders; } private int updating = 0; private int oldEventMask = 0; public new TextAlignment SelectionAlignment {//SelectionAlignment is not overridable get { PARAFORMAT2 pf = new PARAFORMAT2(); pf.cbSize = Marshal.SizeOf(pf); SendMessage(this.Handle, EM_GETPARAFORMAT, SCF_SELECTION, ref pf); if ((pf.dwMask & PFM_ALIGNMENT) == 0) return TextAlignment.Left; return (TextAlignment)pf.wAlignment; } set { PARAFORMAT2 pf = new PARAFORMAT2(); pf.cbSize = Marshal.SizeOf(pf); pf.dwMask = PFM_ALIGNMENT; pf.wAlignment = (short)value; SendMessage(this.Handle, EM_SETPARAFORMAT, SCF_SELECTION, ref pf); } } //Overrides OnHandleCreated to enable RTB advances options protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); //EM_SETTYPOGRAPHYOPTIONS allows to enable RTB (RichEdit) Advanced Typography SendMessage(this.Handle, EM_SETTYPOGRAPHYOPTIONS, TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY); } } //JustifiedRichTextBox