Page 1 of 2 12 Last
  • Jump to page:
    #1
  1. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2005
    Location
    Northwest Florida
    Posts
    208
    Rep Power
    15

    Is it possible to make an autosizing textbox?


    I have a textbox that starts off a certain size. But, if the user types in enough text to require scrolling in the textbox, then I want to resize the textbox to contain all the text so that scrolling isn't necessary.

    For most controls, I would simply use the Control.DisplayRectangle.Size property, but when used for a textbox, this is just the size of the textbox itself, regardless of whether the text extends beyond the bounds of the textbox or not.

    I have tried using Graphics.MeasureString to get the size I need, and I get close but not quite what I need for professional results. And I know the textbox already knows the size I'm looking for, otherwise, it couldn't produce proper scrolling behavior when the text goes beyond the bounds of the textbox.

    Any ideas?

    --webdunce
    Last edited by WebDunce; March 15th, 2009 at 07:25 PM. Reason: better title
  2. #2
  3. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2009
    Location
    Lodz, Poland
    Posts
    31
    Rep Power
    10

    A possible solution and spooky idea


    I can only suggest using that Graphics.MeasureString method with some additional margin. I scratched this code specially for you, so please tell me what you think.

    Uploaded the class, along with runnable example:
    speedyshare.com/181911114.html

    An idea, if it's worth anything. When you make a class based on TextBox, you can override (and access?) some hidden properties. One such property is AutoSize, but this one is not relevant in this case. If you find a relevant member for solution, then create a class derived from Textbox (like AutosizeTextbox : TextBox).

    C# Code:
     
    /// <summary>
    /// Use this method only once, per textbox. Created event-handler will keep 
    /// track of text changes, and autosize textbox automaticaly. 
    /// The +6 makes a margin of error. You can customize the number if needed. 
    /// </summary>
    public static void KeepAutoresizingThis(TextBox textbox)
    {
        Graphics graphics = textbox.CreateGraphics();
        Size originalSize = textbox.Size;
     
        textbox.TextChanged += new EventHandler(delegate
            {
                int suggestedWidth =
                    (int)graphics.MeasureString(textbox.Text, textbox.Font).Width
                    + 6;
     
                if (suggestedWidth < originalSize.Width)
                    textbox.Size = originalSize;
                else
                    textbox.Width = suggestedWidth;
            });
    }
    Last edited by ArekBulski; March 13th, 2009 at 03:49 PM. Reason: a note of overriding
  4. #3
  5. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2005
    Location
    Northwest Florida
    Posts
    208
    Rep Power
    15
    thanks so much for the reply.

    I am so sorry. I neglected to mention that the textbox will be of fixed width. So, I need to resize the height. I am currently using Graphics.MeasureString and I do add a bottom padding because Graphics.MeasureString seems to be getting a different height than I need (sometimes a bit smaller, sometimes a bit taller...depending on the actual text).

    currently I'm using a method that returns a Size object (from which i get the Height property)
    Code:
            Size GetTextSize(TextBox tb)
            {
                // initialize some variables
                Graphics g = tb.CreateGraphics();
                SizeF size = g.MeasureString(tb.Text, tb.Font, tb.Width - 6); // the -6 should account for the diff between the textbox.clientrectangle.width and the textbox.width
                int bottomPadding = 20;
                int desiredHeight = (int)size.Height + 7 + bottomPadding; // the +7 should account for the diff between the textbox.clientrectangle.height and textbox.height
                int heightOfSingleLineOfText = tb.Font.Height;
    
                // if there are any characters at all...
                if (tb.TextLength > 0)
                {
                    // and if the last char is a newline...
                    if (tb.Text[tb.TextLength - 1] == '\n')
                    {
                        // add an extra line
                        desiredHeight += heightOfSingleLineOfText;
                    }
                }
    
                // cycle through the characters
                for (int i = 0; i < tb.TextLength; i++)
                {
                    // if the char is a newline
                    // and is preceded by a newline...
                    if (tb.Text[i] == '\n' && tb.Text[i - 1] == '\n')
                    {
                        // add an extra "line"
                        desiredHeight += heightOfSingleLineOfText;
                    }
                }
    
                size.Height = desiredHeight;
                return new Size((int)size.Width, (int)size.Height);
            }
    but this is just not as nice as it could be if i could access the same rectangle that the textbox itself has already calculated and painted the text in.

    thanks.
    Last edited by WebDunce; March 13th, 2009 at 04:04 PM.
  6. #4
  7. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2005
    Location
    Northwest Florida
    Posts
    208
    Rep Power
    15
    Also, I definitely thought about deriving a custom textbox control to access the protected properties and methods but i couldn't seem to find a property or method that seemed to do what I need


    there might be one....i just couldn't find it.

    Thanks again. All suggestions and ideas are definitely appreciated.
  8. #5
  9. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2005
    Location
    Northwest Florida
    Posts
    208
    Rep Power
    15
    I have found a workaround....

    RichTextBox has a ContentsResized event that provides the rectangle I am seeking.

    However, I must use a textbox because it is already a heavily customized textbox and the functionality that I added to the textbox I have no intention of spending the time to so customize a RichTextBox object.

    But, I can add a RichTextBox to the Form's class (but not add it to the form's controls, see, so it is not visible).

    Anytime the textbox's text changes, i will update the richtextbox and anytime the richtextbox fires the ContentsChanged event, i will use that to update the size of the textbox...it works...i tried it already.

    Code:
    // the form was designed in the designer so the 
    // textbox1 object was created in the designer and
    // it's code is not shown here
    public partial class Form1 : Form
        {
            RichTextBox rtb = new RichTextBox();
    
            public Form1()
            {
                InitializeComponent();
                rtb.Size = textBox1.Size;            
                textBox1.TextChanged += new EventHandler(textBox1_TextChanged);
                rtb.ContentsResized += new ContentsResizedEventHandler(rtb_ContentsResized);
            }
    
            void rtb_ContentsResized(object sender, ContentsResizedEventArgs e)
            {
                textBox1.Height = e.NewRectangle.Height + 7; // +7 accounts for some diff between the textbox's clientsize and actual size.
            }
    
    
            private void textBox1_TextChanged(object sender, EventArgs e)
            {
                rtb.Text = this.textBox1.Text;
            }
  10. #6
  11. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2009
    Location
    Lodz, Poland
    Posts
    31
    Rep Power
    10

    Excellent solution, but incomplete?


    I am impressed. You seem to be very efficient with finding solutions. However I wasn't able to make your code working, without adding few lines. Also it needs to change RichTextBox.ScrollBars, or more text will cause over-grow of that e.NewRectangle. Here is my code, based on your excellent solution:

    Code:
    public partial class Form1 : Form
        {
            RichTextBox rtb = new RichTextBox();
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                rtb.Parent = this;  /// needed to add this!
                rtb.Visible = false;  /// needed as well!
                rtb.Size = textBox1.Size;
                rtb.ScrollBars = RichTextBoxScrollBars.None; /// also this is important!
    
                textBox1.TextChanged += new EventHandler(textBox1_TextChanged);
                rtb.ContentsResized += new ContentsResizedEventHandler(rtb_ContentsResized);
            }
    
            void rtb_ContentsResized(object sender, ContentsResizedEventArgs e)
            {
                textBox1.Height = e.NewRectangle.Height + 7;
            }
    
            void textBox1_TextChanged(object sender, EventArgs e)
            {
                rtb.Text = textBox1.Text;
            }
        }
    Would you want some help with putting this into a custom control? So you can add this custom-textbox from Designer Toolbox, without re-writing this code for every new textbox.
  12. #7
  13. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2005
    Location
    Northwest Florida
    Posts
    208
    Rep Power
    15
    Hey ArekBulski,

    Thanks for your code. Your improvements to my code are very good and I will use them.
  14. #8
  15. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2009
    Location
    Lodz, Poland
    Posts
    31
    Rep Power
    10
    Originally Posted by WebDunce
    Thanks for your code. Your improvements to my code are very good and I will use them.
    Ah, thanks! Me feels appreciated...

    So what is your project all about? I am really curious. Besides I think I got too much spare time, so why not to spend some time here, working on some more solutions for you?
  16. #9
  17. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2005
    Location
    Northwest Florida
    Posts
    208
    Rep Power
    15
    (anyone thinking of using this code should first read post #13)

    also, here is my attempt at a custom control. It seems to work good. You may improve it....

    Code:
        public partial class AutoSizingTextBox : UserControl
        {
            RichTextBox rtb = new RichTextBox();
            TextBox tb = new TextBox();
    
            [Browsable(true)]
            public new int Height
            {
                get { return this.tb.Height; }
                set { this.tb.Height = value; }
            }
    
            [Browsable(true)]
            public new int Width
            {
                get { return this.tb.Width; }
                set { this.tb.Width = value; }
            }
    
            public AutoSizingTextBox()
            {
                InitializeComponent();
                this.Size = this.tb.Size;
    
                this.rtb.Location = new Point(0, 0);
                this.rtb.ScrollBars = RichTextBoxScrollBars.None;
                this.rtb.Size = tb.Size;
                this.rtb.ContentsResized += new ContentsResizedEventHandler(rtb_ContentsResized);
                this.Controls.Add(rtb);
    
                this.tb.Size = this.Size;
                this.tb.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom;
                this.tb.Location = new Point(0, 0);
                this.tb.Multiline = true;
                this.tb.TextChanged += new EventHandler(tb_TextChanged);
                this.tb.SizeChanged += new EventHandler(tb_SizeChanged);
                this.Controls.Add(tb);
                this.tb.BringToFront();
            }       
    
            void tb_SizeChanged(object sender, EventArgs e)
            {
                if (this.rtb.Width != this.tb.Width)
                {
                    this.rtb.Width = this.tb.Width;
                    this.rtb.Height = this.tb.Height - 8;
                }
    
                this.Size = this.tb.Size;
            }
    
            void tb_TextChanged(object sender, EventArgs e)
            {
                this.rtb.Text = this.tb.Text;
            }
    
            void rtb_ContentsResized(object sender, ContentsResizedEventArgs e)
            {
                this.rtb.Height = e.NewRectangle.Height;            
                this.tb.Height = this.rtb.Height + 8;
            }
        }
    Last edited by WebDunce; March 18th, 2009 at 10:35 PM.
  18. #10
  19. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2005
    Location
    Northwest Florida
    Posts
    208
    Rep Power
    15
    Originally Posted by ArekBulski
    Ah, thanks! Me feels appreciated...

    So what is your project all about? I am really curious. Besides I think I got too much spare time, so why not to spend some time here, working on some more solutions for you?
    Several years ago, I designed a website for a friend of mine. I built the site in such a way that he cannot easily edit it with Dreamweaver or Frontpage or a Flash editor.

    I am trying to create an editor that will let my friend edit the website. An autosizing textbox will be an important part of the editor program (which I have spent years working on... )
  20. #11
  21. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2009
    Location
    Lodz, Poland
    Posts
    31
    Rep Power
    10
    Originally Posted by WebDunce
    I am trying to create an editor that will let my friend edit the website. An autosizing textbox will be an important part of the editor program (which I have spent years working on... )
    I am even more amazed now. Wish you good luck, and of course I will be here, trying to advise when you need it...

    also, here is my attempt at a custom control. It seems to work good. You may improve it....
    As for this class... it really works! I tried to make a class derived from TextBox, instead from UserControl. It is smaller, but I won't hide that it does not work. wtf

    C# Code:
     
    public class AutoSizingTextBox : TextBox
        {
            RichTextBox rtb = new RichTextBox();
     
            public AutoSizingTextBox()
            {
                this.Multiline = true;
                this.TextChanged += new EventHandler(AutoSizingTextBox_TextChanged);
                this.SizeChanged += new EventHandler(AutoSizingTextBox_SizeChanged);
                //this.FindForm().Controls.Add(this);
     
                rtb.Location = new Point(0, 0);
                rtb.Parent = this.Parent;
                rtb.Visible = false;
                rtb.ScrollBars = RichTextBoxScrollBars.None;
                rtb.Size = this.Size;
                rtb.ContentsResized += new ContentsResizedEventHandler(rtb_ContentsResized);
                //this.FindForm().Controls.Add(rtb);
            }
     
            void AutoSizingTextBox_SizeChanged(object sender, EventArgs e)
            {
                rtb.Size = this.Size;
            }
     
            void AutoSizingTextBox_TextChanged(object sender, EventArgs e)
            {
                rtb.Text = this.Text;
            }
     
            void rtb_ContentsResized(object sender, ContentsResizedEventArgs e)
            {
                rtb.Height = e.NewRectangle.Height;
                this.Height = e.NewRectangle.Height + 8;
            }
        }
  22. #12
  23. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2005
    Location
    Northwest Florida
    Posts
    208
    Rep Power
    15
    just as a note to myself and anyone else who may be trying to create an autosizing textbox...a google search has turned up a bit of code that uses windows messages and centers around the EM_GETLINECOUNT message.

    i haven't had a chance to really look into this but it looks promising...

    Click here for the code example.
  24. #13
  25. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2005
    Location
    Northwest Florida
    Posts
    208
    Rep Power
    15
    so i rewrote my AutosizeableTextbox based on the code i referenced in the above post that i found in a google search. it uses the SendMessage() method and is much more efficient than the original usercontrol i made by combining a textbox and a richtextbox. You have to link to the user32.dll and use the EM_GETLINECOUNT message

    c# Code:
     
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Forms;
    using System.Drawing;
    using System.ComponentModel;
    using System.Runtime.InteropServices;
     
     
    namespace AutoSizeableTextBox
    {
        public class AutosizeableTextbox : TextBox
        {
            // link to the SendMessage function in the user32.dll
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
            static extern IntPtr SendMessage(IntPtr hWnd, Int32 Msg, IntPtr wParam, IntPtr lParam);
     
            // set up some variables
            const int TEXTBOX_PADDING = 7;
            int minWidth = 10;  // during instantiation, this is set to the width of a double-u
            int minHeight = 20;
            AutoSizeMode autosizeMode = AutoSizeMode.GrowOnly;
            bool autosize = true;
     
            // set up some useful properties
            public int MinimumWidth
            { get { return this.minWidth; } }
     
            public int MinimumHeight
            { get { return this.minHeight; } }
     
            [Browsable(true), Category("Autosize Properties")]
            public AutoSizeMode AutoSizeMode2 // see note by AutoSize2
            {
                get { return autosizeMode; }
                set
                {
                    autosizeMode = value;
                    ResizeMe();
                }
            }
     
            [Browsable(true), Category("Autosize Properties")]
            public bool AutoSize2 // when i called it AutoSize, whether i used "new" or "override," it would always reset itself to false at runtime.
            {
                get { return autosize; }
                set
                {
                    autosize = value;
                    ResizeMe();
                }
            }
     
     
            // constructor
            public AutosizeableTextbox()
            {
                this.minWidth = TextRenderer.MeasureText("W", this.Font).Width;
                this.minHeight = (int)(this.FontHeight + TEXTBOX_PADDING);
                //this.MinimumSize = new Size(this.MinimumSize.Width, minHeight);
                this.FontChanged += new EventHandler(AutosizeableTextbox_FontChanged);
                this.TextChanged += new EventHandler(AutosizingTextbox_TextChanged);
                this.SizeChanged += new EventHandler(AutosizeableTextbox_SizeChanged);
     
            }
     
            void AutosizeableTextbox_FontChanged(object sender, EventArgs e)
            {
                this.minWidth = TextRenderer.MeasureText("W", this.Font).Width;
                this.minHeight = (int)(this.FontHeight + TEXTBOX_PADDING);
                ResizeMe();
            }
     
            // public methods
            public void ForceResize()
            {
                ResizeMe();
            }
     
            public void ForceResize(int minWidth, int minHeight, int width, int height)
            {
                this.MinimumSize = new Size(minWidth, minHeight);
                this.Size = new Size(width, height);
                ResizeMe();
            }
     
            // event handlers
            void AutosizeableTextbox_SizeChanged(object sender, EventArgs e)
            {
                ResizeMe();
            }
     
            void AutosizingTextbox_TextChanged(object sender, EventArgs e)
            {
                ResizeMe();
            }
     
            // resize method
            void ResizeMe()
            {
                if (this.MinimumSize.Height < this.minHeight)
                    this.MinimumSize = new Size(this.MinimumSize.Width, this.minHeight);
     
                if (this.MinimumSize.Width < this.minWidth)
                    this.MinimumSize = new Size(this.minWidth, this.MinimumSize.Height);
     
                if (autosize)
                {
                    if (this.Multiline)
                        ResizeWithMultilineOn();
                    else
                        ResizeWithMultilineOff();
                }
            }
     
            // resize when multiline is true
            void ResizeWithMultilineOn()
            {
                double lineHeight = this.FontHeight;
                int lineCount = this.GetLineCount();
                int newHeight = (int)(lineCount * lineHeight) + TEXTBOX_PADDING;
     
                if (newHeight < this.Height)
                {
                    if (autosizeMode == AutoSizeMode.GrowAndShrink)
                        this.Height = newHeight;
                }
                else
                {
                    this.Height = newHeight;
                }
            }
     
            // resize when multiline is false
            void ResizeWithMultilineOff()
            {
                if (this.TextLength > 0)
                {
                    int newWidth = TextRenderer.MeasureText(this.Text, this.Font).Width + this.minWidth;
                    if (newWidth < this.Width)
                    {
                        if (autosizeMode == AutoSizeMode.GrowAndShrink)
                        {
                            this.Width = newWidth;
                        }
                    }
                    else
                    {
                        this.Width = newWidth;
                    }
                }
                else
                {
                    if (autosizeMode == AutoSizeMode.GrowAndShrink)
                    {
                        this.Width = this.minWidth; // width of a double-u
                    }
                }
            }
     
            // gets the number of lines in the textbox
            int GetLineCount()
            {
                const Int32 EM_GETLINECOUNT = 186;
                return (int)SendMessage(this.Handle, EM_GETLINECOUNT, IntPtr.Zero, IntPtr.Zero);
            }
        }
    }


    Then you have to multiply the number of lines by the height of the textbox's fontheight and add a little extra for padding etc, but it works just fine.

    click here for a demo project using the autosizing textbox (the link will not work after 7 days of no downloads...so some may find the link broken in the future)
    Last edited by WebDunce; March 18th, 2009 at 08:32 AM.
  26. #14
  27. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2009
    Location
    Lodz, Poland
    Posts
    31
    Rep Power
    10
    I tested WebDunce's code and it really works! Congrats!
    Thanks for uploading the whole solution too!

    Best regards,
    Arek Bulski.
  28. #15
  29. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jan 2012
    Posts
    1
    Rep Power
    0

    Thumbs up Very Nice! Thanks!


    I registered with Dev Shed just so I could give kudos to WebDunce. I swapped your control in place of a standard textbox and voila: not a hitch. You may be a dunce at web, but not at desktop app development! Thanks very much for contributing this!
Page 1 of 2 12 Last
  • Jump to page:

IMN logo majestic logo threadwatch logo seochat tools logo