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
    12

    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 08: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
    6

    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 04: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
    12
    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 05: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
    12
    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
    12
    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
    6

    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
    12
    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
    6
    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
    12
    (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 11: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
    12
    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
    6
    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
    12
    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
    12
    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 09: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
    6
    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