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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/791830/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 01:17:53  来源:igfitidea点击:

Autoscale Font in a TextBox Control so that its as big as possible and still fits in text area bounds

c#.netwinforms

提问by blak3r

I need a TextBox or some type of Multi-Line Label control which will automatically adjust the font-size to make it as large as possible and yet have the entire message fit inside the bounds of the text area.

我需要一个 TextBox 或某种类型的 Multi-Line Label 控件,它会自动调整字体大小以使其尽可能大,同时使整个消息适合文本区域的边界。

I wanted to see if anyone had implemented a user control like this before developing my own.

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

Example application: have a TextBox which will be half of the area on a windows form. When a message comes in which is will be approximately 100-500 characters it will put all the text in the control and set the font as large as possible. An implementation which uses Mono Supported .NET libraries would be a plus.

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

If know one has implemented a control already... If someone knows how to test if a given text completely fits inside the text area that would be useful for if I roll my own control.

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

Edit: I ended up writing an extension to RichTextBox. I will post my code shortly once i've verified that all the kinks are worked out.

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

采纳答案by blak3r

The solution i came up with was to write a control which extends the standard RichTextBox control.

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

Use the extended control in the same way you would a regular RichTextBox control with the following enhancements:

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

  • Call the ScaleFontToFit() method after resizing or text changes.
  • The Horizontal Alignment field can be used to center align the text.
  • The Font attributes set in the designer will be used for the entire region. It is not possible to mix fonts as they will changed once the ScaleFontToFit method is called.
  • 调整大小或更改文本后调用 ScaleFontToFit() 方。
  • 水平对齐字段可用于居中对齐文本。
  • 在设计器中设置的字体属性将用于整个区域。不能混合字体,因为一旦调用 ScaleFontToFit 方它们就会改变。

This control combines several techniques to determine if the text still fits within it's bounds. If the text area is multiline, it detects if scrollbars are visible. I found a clever way to detect whether or not the scrollbars are visible without requiring any winapi calls using a clever technique I found on one of Patrick Smacchia's posts.. When multiline isn't true, vertical scrollbars never appear so you need to use a different technique which relies on rendering the text using a the Graphics object. The Graphic rendering technique isn't suitable for Multiline boxes because you would have to account for word wrapping.

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

Here are a few snippets which shows how it works (link to source code is provided below). This code could easily be used to extend other controls.

这里有一些片段展示了它是如何工作的(下面提供了源代码的链接)。此代码可轻松用于扩展其他控件。

    /// <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 could be optimized to improve performance but I kept it simple so it'd be easy to understand.

ScaleFontToFit 可以优化以提高性能,但我保持简单,以便于理解。

Download the latest source code here.I am still actively working on the project which I developed this control for so it's likely i'll be adding a few other features and enhancements in the near future. So, check the site for the latest code.

在这里下载最新的源代码。我仍在积极致力于我开发此控件的项目,因此我可能会在不久的将来添加一些其他功能和增强功能。因此,请检查站点以获取最新代码。

My goal is to make this control work on Mac using the Mono framework.

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

回答by Steven Richards

I haven't seen an existing control to do this, but you can do it the hard way by using a RichTextBox and the TextRenderer's MeasureText method and repeatedly resizing the font. It's inefficient, but it works.

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

This function is an event handler for the 'TextChanged' event on a RichTextBox.

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

An issue I've noticed:

我注意到的一个问题:

When typing, the text box will scroll to the current caret even if scrollbars are disabled. This can result in the top line or left side getting chopped off until you move back up or left with the arrow keys. The size calculation is correct assuming you can get the top line to display at the top of the text box. I included some scrolling code that helps sometimes (but not always).

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

This code assumes word wrap is disabled. It may need modification if word wrap is enabled.

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



The code:

编码:

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

回答by GinoA

I had to solve the same basic problem. The iterative solutions above were very slow. So, I modified it with the following. Same idea. Just uses calculated ratios instead of iterative. Probably, not quite as precise. But, much faster.

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

For my one-off need, I just threw an event handler on the label holding my text.

对于我的一次性需要,我只是在包含我的文本的标签上抛出了一个处理程序。

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

回答by zackrspv

I had a similar requirement for a text box in a panel on a windows form hosted window. (I injected the panel onto the existing form). When the size of the panel changes (in my case) the text would resize to fit the box. Code

我对 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);

                }
            };