【问题标题】:How to justify text in a label如何对齐标签中的文本
【发布时间】:2016-09-06 09:58:22
【问题描述】:

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

【问题讨论】:

  • 你的意思是块对齐吗?不支持。
  • 将文本分成两个标签。
  • 我的意思是块对齐。 @TaW,将您的评论更改为答案,以便我考虑已回答的问题。
  • 嗯...我不明白“块对齐”。如果您的标签有多行、固定大小并且您使用TextAlign,那么与您要归档的内容有什么不同?
  • 完成。我明天可能会尝试编写解决方法,但没有时间 atm..@Pikoh Block 或 Justify 意味着两个边框都像书中一样对齐。

标签: c# .net winforms label justify


【解决方案1】:

很遗憾,仅支持三种最基本和最简单的对齐方式:RightLeftCenter

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

唯一的解决方法是在单词之间添加空格或更小的空格字符,例如thin space(U+2009) 或hair space (U+200A),即在常规空格之后直到Label' s Height 更改。然后后退一步,尝试找到下一个插入点,即下一行,以此类推..直到到达文本的末尾。

有点棘手,但不是特别难。

【讨论】:

    【解决方案2】:

    另一个实现。
    这个仅在单词之间插入“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'});
    }
    


    worker方法:

    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 类,并且该类支持“Justification”。
    这是在旧的 Platform SDK 中报告的(带有示例)。
    RichTextBox 的AdvancedTypographicsOption 在句柄创建期间被显式截断。
    (这与实现 PARAFORMATPARAFORMAT2 结构无关,这无关紧要,这是故意的)。

    所以这是对可怜的 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
    


    【讨论】:

    • 这太棒了,但可以修改它以获得“
      ”而不是 rn?我正在使用 HTML 标记。我试过了,但无法完成。我正在与 RDLC 合作,几乎无法解决这个令人难以置信的问题。提前感谢您的帮助,或者如果您愿意,我可以提出一个新问题。
    • @MacGyver 我不太确定你在这里问什么。是否需要将\r\n\n 替换为&lt;/br&gt;&lt;br&gt;?这应该什么时候发生?当文本导出为 Html 时?打字的时候?也许提出一个问题是个好主意,这样您就可以更好地描述您的要求。这可能很有趣。
    • 感谢您的回复,我真的做到了,这是为了 RDLC html 标记 :)
    【解决方案3】:

    这是 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 出于某种原因,我无法确定间距字符的大小。我将发布一个涉及单词间距的解决方案。
    • 嗨@Manfred Radlwimmer! Mac OS 中的 TextRenderer 怎么样,有类似的吗?
    • 这是个坏人,我想要并且需要证明标签中的文本,也许我可以用另一种方式取行的长度来替换跨平台的Testrenderer.MeasureText(...),@ManfredRadlwimmer ...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-31
    • 2013-09-25
    • 2010-12-22
    相关资源
    最近更新 更多