如何对齐标签中的文本

How to justify text in a label

我有一个显示在多行上的标签,我想对齐其中的文本(左右对齐)。实现该目标的最佳方法是什么?

遗憾的是,仅支持三种最基本和最简单的对齐类型:RightLeftCenter

第四个 JustifiedBlock 在任何 .NET 控件 afaik 中都不支持,甚至在 RichtTextBox 中也不支持:-(

唯一的解决方法是在单词之间添加空格或更小的空白字符,如 thin space(U+2009) 或 hair space (U+200A),即在常规空格之后直到 LabelHeight 改变。然后后退一步并尝试找到下一个插入点,即下一行等等……直到到达文本末尾。

有点棘手,但不是很难。

这是 TaW 提出的解决方案的实现。它只是实现的基本代码 - 没有自动重新调整等。

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

进行中:

另一个实现。
这个只在单词之间插入“Hair Spaces”。

编辑:
添加了实现段落块对齐的方法。
JustifyParagraph()JustifyLine()都调用worker方法Justify().

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() 只处理单行文本:(比客户区短)

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'});
}


工作者方法:

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();
    }))();
}

关于 RichTextBox。
@TaW 说它不支持块对齐,但事实并非如此。
众所周知,RichTextBox 基于 RichEdit class 并且 class 支持“Justification”。
这是在旧平台 SDK 中报告的(带有示例)。
RichTextBox 的 AdvancedTypographicsOption 在句柄创建期间被显式截断。
(这不是关于实现 PARAFORMAT vs. PARAFORMAT2 结构,那是无关紧要的,这是故意的)。

所以这是对可怜的 RichTextBox 的“治疗”。
派生自它并使用 SendMessage 将 EM_SETTYPOGRAPHYOPTIONS 消息发送到基 class 的 class,指定 TO_ADVANCEDTYPOGRAPHY 以重新启用 理由.

它还隐藏了 SelectionAlignment,以添加回缺少的 Justify 选项。

这适用于段落级别或从一点开始。

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