如何在标签中实现文本两端对齐

7

我有一个标签,显示的内容超过一行,我希望将文本两端对齐(左右对齐)。如何最好地实现它?


1
你是指块对齐吗?不支持。 - TaW
将文本分成两个标签。 - Maciej Los
我是指块对齐。@TaW,请将您的评论更改为答案,以便我可以考虑问题已得到解答。 - SteeveDroz
嗯...我不理解“块对齐”。如果你的标签有多行,固定大小并且使用了TextAlign,那么与你想要实现的有何不同? - Pikoh
完成。我可能会尝试编写解决方法,但现在没有时间。@Pikoh“块”或“对齐”意味着两个边框像书本一样对齐。 - TaW
好的,现在我明白了。我认为可以使用MeasureString并调整单词之间的空格来实现。 - Pikoh
3个回答

11

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

编辑:
添加了一个实现段落块对齐的方法。
JustifyParagraph()JustifyLine()都调用工作方法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' });
}

enter image description here
enter image description here

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

enter image description here
enter image description here

工作方法:

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 类,该类支持“对齐”功能。
这在旧的平台 SDK 中有所报道(附有示例)。
RichTextBox 在处理创建期间明确截断了其 AdvancedTypographicsOption
(这与实现 PARAFORMAT vs. PARAFORMAT2 结构体无关,这是故意的,与此无关)。

因此,这是解决 RichTextBox 的“良药”。
从中派生一个类,并使用 SendMessage 向基类发送 EM_SETTYPOGRAPHYOPTIONS 消息,指定 TO_ADVANCEDTYPOGRAPHY 以重新启用 Justification

它还遮盖了 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

enter image description here
enter image description here


这太棒了,但是有可能修改它以获取"<br>"而不是rn吗?我正在使用HTML标记。我尝试过了,但无法完成。我正在使用RDLC,很难解决这个令人难以置信的问题。提前感谢任何帮助,或者如果您愿意,我可以提出一个新问题。 - Leandro Bardelli
@MacGyver,我不太确定你在这里问什么。你需要用</br><br>替换\r\n\n吗?这应该在什么时候发生?当文本导出为Html时?还是在输入时?也许提出一个问题是个好主意,这样你就可以更好地描述你的要求。这可能会很有趣。 - Jimi
感谢您的回复,我已经完成了,它是用于RDLC HTML标记 :) - Leandro Bardelli

10
这是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;
}

在操作中:
演示

+1 那个间距字符的度量出现了一些问题,我将发布一个涉及单词间距的解决方案。 - Jimi
嗨@Manfred Radlwimmer!Mac OS中的TextRenderer是否有类似的功能? - Armando Marques da S Sobrinho
这很糟糕,我想要并需要在我的标签中对齐文本,也许我可以用另一种方式来获取行的长度以替换“Testrenderer.MeasureText(...)”以实现跨平台,@ManfredRadlwimmer... - Armando Marques da S Sobrinho

9

很遗憾,只支持三种最基本和简单的对齐类型:右对齐左对齐居中对齐

第四种两端对齐块对齐在任何 .NET 控件中都不被支持。即使在 RichtTextBox 中也不支持。 :-(

唯一的解决方法是在单词之间添加空格或更好的小空格字符,如细空格(U+2009)或毛发空格 (U+200A),直到 Label Height 改变。然后向后一步,尝试找到下一个插入点,即下一行,依此类推.. 直到达到文本的末尾。

有点棘手,但不是难事。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接