25

Given a WinForms TextBox control with MultiLine = true and AcceptsTab == true, how can I set the width of the tab character displayed?

I want to use this as a quick and dirty script input box for a plugin. It really doesn't need to be fancy at all, but it would be nice if tabs were not displayed as 8 characters wide...

1
  • 1
    You should dispose graphics as well, perhaps put it in using statement. Commented May 16, 2012 at 7:16

6 Answers 6

16

I think sending the EM_SETTABSTOPS message to the TextBox will work.

// set tab stops to a width of 4
private const int EM_SETTABSTOPS = 0x00CB;

[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr h, int msg, int wParam, int[] lParam);

public static void SetTabWidth(TextBox textbox, int tabWidth)
{
    Graphics graphics = textbox.CreateGraphics();
    var characterWidth = (int)graphics.MeasureString("M", textbox.Font).Width;
    SendMessage
        ( textbox.Handle
        , EM_SETTABSTOPS
        , 1
        , new int[] { tabWidth * characterWidth }
        );
}

This can be called in the constructor of your Form, but beware: Make sure InitializeComponents is run first.

Sign up to request clarification or add additional context in comments.

3 Comments

The MSDN link is for Windows CE; I changed it.
I think that EM_SETTABSTOPS measures text in Dialog Template Units (sometimes known as Dialog Logical Units, or DLUs). You should be able to just say "numChars * 4" (since there are 4 horizontal DLUs per character).
@Flydog57 is right, this answer produces the wrong output. It should be numChars * 4.
12

I know you are using a TextBox currently, but if you can get away with using a RichTextBox instead, then you can use the SelectedTabs property to set the desired tab width:

richTextBox.SelectionTabs = new int[] { 15, 30, 45, 60, 75};

Note that these offsets are pixels, not characters.

Comments

11

The example offered is incorrect.

The EM_SETTABSTOPS message expects the tab sizes to be specified in dialog template units and not in pixels. After some digging around, it appears that a dialog template unit equals to 1/4th the average width of the window's character. So you'll need to specify 8 for 2 characters long tabs, 16 for four charachters, and so on.

So the code can be simplified as:

public static void SetTabWidth(TextBox textbox, int tabWidth)
{
    SendMessage(textbox.Handle, EM_SETTABSTOPS, 1, 
            new int[] { tabWidth * 4 });
}

1 Comment

Thanks, Loris, I wasn't aware of this.
6

With the use of extension methods, you can add a new method to the TextBox control class. This is my implementation (including an additional extension method that gives you the coordinates for the current location of the insert caret) from what I gathered from the previous contributors above:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace Extensions
{
    public static class TextBoxExtension
    {
        private const int EM_SETTABSTOPS = 0x00CB;

        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SendMessage(IntPtr h, int msg, int wParam, int[] lParam);

        public static Point GetCaretPosition(this TextBox textBox)
        {
            Point point = new Point(0, 0);

            if (textBox.Focused)
            {
                point.X = textBox.SelectionStart - textBox.GetFirstCharIndexOfCurrentLine() + 1;
                point.Y = textBox.GetLineFromCharIndex(textBox.SelectionStart) + 1;
            }

            return point;
        }

        public static void SetTabStopWidth(this TextBox textbox, int width)
        {
            SendMessage(textbox.Handle, EM_SETTABSTOPS, 1, new int[] { width * 4 });
        }
    }
}

2 Comments

You're welcome! Actually a further refinement that I added to my own code checks to make sure that the TextBox is Multiline and AcceptsTab is enabled.
Thanks, Brien, that's a very nice way to encapsulate this functionality !
4

For anyone who wants different tab widths, I took this approach:

using System.Runtime.InteropServices;

[DllImport("User32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr h, int msg, int wParam, uint[] lParam);
private const int EM_SETTABSTOPS = 0x00CB;

private void InitialiseTabStops()
{
    // Declare relative tab stops in character widths
    var tabs = new uint[] { 2, 2, 4, 8, 2, 32 };

    // Convert from character width to 1/4 character width
    for (int position = 0; position < tabs.Length; position++)
        tabs[position] *= 4;

    // Convert from relative to absolute positions
    for (int position = 1; position < tabs.Length; position++)
        tabs[position] += tabs[position - 1];

    SendMessage(textBox.Handle, EM_SETTABSTOPS, tabs.Length, tabs);
}

Comments

2

this is very useful:

Set tab stop positions for a multiline TextBox control

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.