TextBox控件中的自动缩放字体,使其尽可能大,仍然适合文本区域边界

时间:2009-04-26 23:32:46

标签: c# .net winforms

我需要一个TextBox或某种类型的多行标签控件,它会自动调整字体大小,使其尽可能大,但整个消息都适合文本区域的边界。

我想看看是否有人在开发自己的用户控件之前实现了这样的用户控件。

示例应用程序:有一个TextBox,它将是Windows窗体上一半的区域。当一条消息大约为100-500个字符时,它会将所有文本放在控件中并将字体设置得尽可能大。使用Mono Supported .NET库的实现将是一个优势。

如果知道一个人已经实现了一个控件......如果有人知道如何测试一个给定的文本是否完全适合文本区域,如果我自己控制那么这对文本区域是有用的。

编辑:我最终编写了RichTextBox的扩展。一旦我确认所有的问题都解决了,我会立即发布我的代码。

4 个答案:

答案 0 :(得分:4)

我必须解决同样的基本问题。上面的迭代解决方案非常缓慢。所以,我用以下内容对其进行了修改。同样的想法。只使用计算的比率而不是迭代。可能不太精确。但是,要快得多。

对于我的一次性需求,我只是在标签上扔了一个事件处理程序,并保留了我的文本。

    private void PromptLabel_TextChanged(object sender, System.EventArgs e)
    {
        if (PromptLabel.Text.Length == 0)
        {
            return;
        }

        float height = PromptLabel.Height * 0.99f;
        float width = PromptLabel.Width * 0.99f;

        PromptLabel.SuspendLayout();

        Font tryFont = PromptLabel.Font;
        Size tempSize = TextRenderer.MeasureText(PromptLabel.Text, tryFont);

        float heightRatio = height / tempSize.Height;
        float widthRatio = width / tempSize.Width;

        tryFont = new Font(tryFont.FontFamily, tryFont.Size * Math.Min(widthRatio, heightRatio), tryFont.Style);

        PromptLabel.Font = tryFont;
        PromptLabel.ResumeLayout();
    }

答案 1 :(得分:3)

我没有看到现有的控件来执行此操作,但您可以通过使用RichTextBox和TextRenderer的MeasureText方法并反复调整字体大小来实现。它效率低下,但它确实有效。

此函数是RichTextBox上“TextChanged”事件的事件处理程序。

我注意到的一个问题:

键入时,即使禁用了滚动条,文本框也会滚动到当前的插入符号。这可能导致顶线或左侧被切断,直到您使用箭头键向上或向左移动。假设您可以在文本框的顶部显示顶行,则大小计算是正确的。我提供了一些滚动代码,有时会帮助(但并非总是如此)。

此代码假定自动换行被禁用。如果启用自动换行,可能需要修改。


代码:

[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, uint wMsg, int wParam, uint lParam);

private static uint EM_LINEINDEX = 0xbb;

private void richTextBox1_TextChanged(object sender, EventArgs e)
{
    // If there's no text, return
    if (richTextBox1.TextLength == 0) return;

    // Get height and width, we'll be using these repeatedly
    int height = richTextBox1.Height;
    int width = richTextBox1.Width;

    // Suspend layout while we mess with stuff
    richTextBox1.SuspendLayout();

    Font tryFont = richTextBox1.Font;
    Size tempSize = TextRenderer.MeasureText( richTextBox1.Text, richTextBox1.Font);

    // Make sure it isn't too small first
    while (tempSize.Height < height || tempSize.Width < width)
    {
        tryFont = new Font(tryFont.FontFamily, tryFont.Size + 0.1f, tryFont.Style);
        tempSize = TextRenderer.MeasureText(richTextBox1.Text, tryFont);
    }

    // Now make sure it isn't too big
    while (tempSize.Height > height || tempSize.Width > width)
    {
        tryFont = new Font(tryFont.FontFamily, tryFont.Size - 0.1f, tryFont.Style);
        tempSize = TextRenderer.MeasureText(richTextBox1.Text, tryFont);
    }

    // Swap the font
    richTextBox1.Font = tryFont;

    // Resume layout
    richTextBox1.ResumeLayout();

    // Scroll to top (hopefully)
    richTextBox1.ScrollToCaret();
    SendMessage(richTextBox1.Handle, EM_LINEINDEX, -1, 0);
}

答案 2 :(得分:3)

我想出的解决方案是编写一个扩展标准RichTextBox控件的控件。

使用扩展控件的方式与使用常规RichTextBox控件的方式相同,具有以下增强功能:

  • 调整大小或文本更改后调用ScaleFontToFit()方法。
  • “水平对齐”字段可用于居中对齐文本。
  • 设计器中设置的Font属性将用于整个区域。一旦调用ScaleFontToFit方法,就无法混合字体,因为它们会改变。

此控件结合了多种技术来确定文本是否仍然符合其范围。如果文本区域是多行,则它会检测滚动条是否可见。我找到了一种聪明的方法来检测滚动条是否可见,而不需要使用我在Patrick Smacchia's posts.之一上找到的聪明技术进行任何winapi调用。当多线不为真时,永远不会出现垂直滚动条,因此您需要使用不同的技术,该技术依赖于使用Graphics对象渲染文本。图形渲染技术不适用于多行框,因为您必须考虑自动换行。

以下是一些显示其工作原理的片段(下面提供了源代码的链接)。此代码可以轻松用于扩展其他控件。

    /// <summary>
    /// Sets the font size so the text is as large as possible while still fitting in the text
    /// area with out any scrollbars.
    /// </summary>
    public void ScaleFontToFit()
    {
        int fontSize = 10;
        const int incrementDelta = 5; // amount to increase font by each loop iter.
        const int decrementDelta = 1; // amount to decrease to fine tune.

        this.SuspendLayout();

        // First we set the font size to the minimum.  We assume at the minimum size no scrollbars will be visible.
        SetFontSize(MinimumFontSize);

        // Next, we increment font size until it doesn't fit (or max font size is reached).
        for (fontSize = MinFontSize; fontSize < MaxFontSize; fontSize += incrementDelta)
        {
            SetFontSize(fontSize);

            if (!DoesTextFit())
            {
                //Console.WriteLine("Text Doesn't fit at fontsize = " + fontSize);
                break;
            }
        }

        // Finally, we keep decreasing the font size until it fits again.
        for (; fontSize > MinFontSize && !DoesTextFit(); fontSize -= decrementDelta)
        {
            SetFontSize(fontSize);
        }

        this.ResumeLayout();
    }

    #region Private Methods
    private bool VScrollVisible
    {
        get
        {
            Rectangle clientRectangle = this.ClientRectangle;
            Size size = this.Size;
            return (size.Width - clientRectangle.Width) >= SystemInformation.VerticalScrollBarWidth;
        }
    }

    /**
     * returns true when the Text no longer fits in the bounds of this control without scrollbars.
    */
    private bool DoesTextFit()
    {
            if (VScrollVisible)
            {
                //Console.WriteLine("#1 Vscroll is visible");
                return false;
            }

            // Special logic to handle the single line case... When multiline is false, we cannot rely on scrollbars so alternate methods.
            if (this.Multiline == false)
            {
                Graphics graphics = this.CreateGraphics();
                Size stringSize = graphics.MeasureString(this.Text, this.SelectionFont).ToSize();

                //Console.WriteLine("String Width/Height: " + stringSize.Width + " " + stringSize.Height + "form... " + this.Width + " " + this.Height);

                if (stringSize.Width > this.Width)
                {
                    //Console.WriteLine("#2 Text Width is too big");
                    return false;
                }

                if (stringSize.Height > this.Height)
                {
                    //Console.WriteLine("#3 Text Height is too big");
                    return false;
                }

                if (this.Lines.Length > 1)
                {
                    //Console.WriteLine("#4 " + this.Lines[0] + " (2): " + this.Lines[1]); // I believe this condition could be removed.
                    return false;
                }
            }

            return true;
    }

    private void SetFontSize(int pFontSize)
    {
        SetFontSize((float)pFontSize);
    }

    private void SetFontSize(float pFontSize)
    {
        this.SelectAll();
        this.SelectionFont = new Font(this.SelectionFont.FontFamily, pFontSize, this.SelectionFont.Style);
        this.SelectionAlignment = HorizontalAlignment;
        this.Select(0, 0);
    }
    #endregion

ScaleFontToFit可以进行优化以提高性能,但我保持简单,因此很容易理解。

Download the latest source code here.我仍在积极研究我开发此控件的项目,因此我很可能会在不久的将来添加一些其他功能和增强功能。因此,请检查网站上的最新代码。

我的目标是使用Mono框架在Mac上实现此控件。

答案 3 :(得分:0)

我对Windows窗体托管窗口的面板中的文本框有类似的要求。 (我将面板注入现有表格)。当面板的大小发生变化时(在我的情况下),文本会调整大小以适应框。代码

parentObject.SizeChanged += (sender, args) =>
{
   if (textBox1.Text.Length > 0)
   {
       int maxSize = 100;

       // Make a Graphics object to measure the text.
       using (Graphics gr = textBox1.CreateGraphics())
       {
            for (int i = 1; i <= maxSize; i++)
            {
                 using (var test_font = new Font(textBox1.Font.FontFamily, i))
                   {
                        // See how much space the text would
                        // need, specifying a maximum width.
                        SizeF text_size =
                                    TextRenderer.MeasureText(
                                        textBox1.Text,
                                        test_font,
                                        new Size(textBox1.Width, int.MaxValue),
                                        TextFormatFlags.WordBreak | TextFormatFlags.TextBoxControl);

                                try
                                {
                                    if (text_size.Height > textBox1.Height)
                                    {
                                        maxSize = i - 1;
                                        break;
                                    }
                                }
                                catch (System.ComponentModel.Win32Exception)
                                {
                                    // this sometimes throws a "failure to create window handle" error.
                                    // This might happen if the TextBox is invisible and/or
                                    // too small to display a toolbar.
                                    // do whatever here, add/delete, whatever, maybe set to default font size?
                                    maxSize = (int) textBox1.Font.Size;
                                }
                            }
                        }
                    }

                    // Use that font size.
                    textBox1.Font = new Font(textBox1.Font.FontFamily, maxSize);

                }
            };