20

Is there any way to change the default tab size in a .NET RichTextBox? It currently seems to be set to the equivalent of 8 spaces which is kinda large for my taste.

Edit: To clarify, I want to set the global default of "\t" displays as 4 spaces for the control. From what I can understand, the SelectionTabs property requires you to select all the text first and then the the tab widths via the array. I will do this if I have to, but I would rather just change the global default once, if possible, sot that I don't have to do that every time.

nalply
  • 26,770
  • 15
  • 78
  • 101
Adam Haile
  • 30,705
  • 58
  • 191
  • 286

5 Answers5

24

You can set it by setting the SelectionTabs property.

private void Form1_Load(object sender, EventArgs e)
{
    richTextBox1.SelectionTabs = new int[] { 100, 200, 300, 400 };
}

UPDATE:
The sequence matters....

If you set the tabs prior to the control's text being initialized, then you don't have to select the text prior to setting the tabs.

For example, in the above code, this will keep the text with the original 8 spaces tab stops:

richTextBox1.Text = "\t1\t2\t3\t4";
richTextBox1.SelectionTabs = new int[] { 100, 200, 300, 400 };

But this will use the new ones:

richTextBox1.SelectionTabs = new int[] { 100, 200, 300, 400 };
richTextBox1.Text = "\t1\t2\t3\t4";
Scott Nichols
  • 6,108
  • 4
  • 28
  • 23
  • 5
    It might be useful to add that these values are the tab stop *in pixels* rather than in characters. It is mentioned on the [MSDN page](http://msdn.microsoft.com/en-us/library/system.windows.forms.richtextbox.selectiontabs.aspx) but seems somewhat counter-intuitive. – JYelton Oct 11 '12 at 16:17
  • Any ideas on how to calculate the tab stops automatically for a given monospaced font to avoid hard-coding the tab stops like above? – sɐunıɔןɐqɐp Mar 26 '21 at 11:05
5

Winforms doesn't have a property to set the default tab size of a RichTexBox with a single number, but if you're prepared to dig into the Rtf of the rich text box, and modify that, there's a setting you can use called: "\deftab". The number afterwards indicates the number of twips (1 point = 1/72 inch = 20 twips). The resulting Rtf with the standard tab size of 720 twips could look something like:

{\rtf1\ansi\ansicpg1252\deff0\deflang2057\deftab720{\fonttbl{\f0\fnil\fcharset0 Microsoft Sans Serif;}}
\viewkind4\uc1\pard\f0\fs41
1\tab 2\tab 3\tab 4\tab 5\par
}

If you need to convert twips into pixels, use this code inspired from Convert Pixels to Points:

int tabSize=720;
Graphics g = this.CreateGraphics();
int pixels = (int)Math.Round(((double)tabSize) / 1440.0 * g.DpiX);
g.Dispose();
Community
  • 1
  • 1
Dan W
  • 3,520
  • 7
  • 42
  • 69
3

It's strange that no one has proposed this method for all this time)

We can inherit from the RichTextBox and rewrite the CmdKey handler (ProcessCmdKey)
It will look like this:

public class TabRichTextBox : RichTextBox
{
    [Browsable(true), Category("Settings")]
    public int TabSize { get; set; } = 4;

    protected override bool ProcessCmdKey(ref Message Msg, Keys KeyData)
    {
            
        const int WM_KEYDOWN = 0x100;       // https://learn.microsoft.com/en-us/windows/desktop/inputdev/wm-keydown
        const int WM_SYSKEYDOWN = 0x104;    // https://learn.microsoft.com/en-us/windows/desktop/inputdev/wm-syskeydown
        // Tab has been pressed
        if ((Msg.Msg == WM_KEYDOWN || Msg.Msg == WM_SYSKEYDOWN) && KeyData.HasFlag(Keys.Tab))
        {
            // Let's create a string of spaces, which length == TabSize
            // And then assign it to the current position
            SelectedText += new string(' ', TabSize);

            // Tab processed
            return true;
        }
        return base.ProcessCmdKey(ref Msg, KeyData);
    }
}

Now, when you'll press Tab, a specified number of spaces will be inserted into the control area instead of \t

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196
Kir_Antipov
  • 131
  • 1
  • 4
  • 1
    Tabs are used for distances. Your "spaces" would only half-way work if the end user is using a mono-spaced font. – LarsTech Aug 31 '18 at 21:32
  • This worked perfectly. One small issue. I had to change the KeyData.HasFlag(Keys.Tab) to KeyData == Keys.Tab – robert Oct 30 '18 at 21:09
  • 1
    This is NOT how tabs work. A tab is not equivalent to a given number of spaces. Rather, a tab character advances to the next tab stop. Even in a mono-spaced font, suppose there is a tab stop every 8 characters. Type 3 characters, then a tab: FIVE spaces will be advanced. Equally importantly, if its an editable box, and user goes back and adds another 2 characters, now the tab will only be THREE spaces (8-(3+2) = 3). This proposal is broken in both of those cases. – ToolmakerSteve Mar 23 '21 at 23:20
  • @ToolmakerSteve, oh, really? Fascinating! This answer is an example of CmdKey handling, so implementation stays as simple as possible (there's no even `Shift + Tab` available). Don't like this behavior? Of course you don't! It's incomplete. Go ahead and implement one you'd like (which will be opinion-based, since, for example, I know too little IDEs/Text Editors that implement your second case for space users) using CmdKey processing knowledge. Just that simple – Kir_Antipov Mar 23 '21 at 23:39
  • 1
    OK. Bottom line is that it is impossible to fully mimic how tab stops work, using this technique. Because you've thrown away the fact that it was a tab. And given that RichTextBox has a perfectly fine solution for specifying tab stops, I don't see this as an answer to *this* question. Sorry if this sounds harsh. – ToolmakerSteve Mar 23 '21 at 23:48
  • 1
    @ToolmakerSteve, nope, it's ok. As I said before, it's opinion based, so the question may trigger space vs tabs holywars. And you're clearly tab-adherent aka "you can't replace tabs with spaces". You do you, nobody judges – Kir_Antipov Mar 23 '21 at 23:56
  • Good point. I didn't think about situations where the coding standard is to convert all tabs to spaces. Now that I think about it, I've seen that often for web languages. For that usage, the "enhancement" would be to change from a fixed number of spaces (TabSize), to a number of spaces that takes to next tab stop. Something like `new string(' ', (charsToLeft - 1) % TabSize) + 1)` – ToolmakerSteve Mar 25 '21 at 21:20
1

If you have a RTF box that is only used to display (read only) fixed pitch text, the easiest thing would be not to mess around with Tab stops. Simply replace them stuff with spaces.

If you want that the user can enter something and use that Tab key to advance you could also capture the Tab key by overriding OnKeyDown() and print spaces instead.

Elmue
  • 7,602
  • 3
  • 47
  • 57
  • 1
    Unless you have 14pt spaces, bold spaces or otherwise formatted spaces. – Thomas Weller Jan 08 '15 at 13:41
  • You are right. I forgot to mention that this makes mainly sense for code editors that use 'Courier New' and only one font size. In 'Courier New' a bold space has the same width a normal space. But if you are using variable pitch fonts or different font sizes it would not be a good idea to replace tabs with spaces. – Elmue Jan 14 '15 at 16:58
0

I'm using this class with monospaced fonts; it replaces all TABs with spaces.

All you have to do is to set the following designer properties according to your requirements:

  • AcceptsTab = True TabSize
  • ConvertTabToSpaces = True
  • TabSize = 4

PS: As @ToolmakerSteve pointed out, obviously the tab size logic here is very simple: it just replaces tabs with 4 spaces, which only works well for tabs at the beginning of each line. Just extend the logic if you need improved tab treatment.

Code

using System.ComponentModel;
using System.Windows.Forms;

namespace MyNamespace
{
    public partial class MyRichTextBox : RichTextBox
    {
        public MyRichTextBox() : base() =>
            KeyDown += new KeyEventHandler(RichTextBox_KeyDown);

        [Browsable(true), Category("Settings"), Description("Convert all tabs into spaces."), EditorBrowsable(EditorBrowsableState.Always), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public bool ConvertTabToSpaces { get; set; } = false;

        [Browsable(true), Category("Settings"), Description("The number os spaces used for replacing a tab character."), EditorBrowsable(EditorBrowsableState.Always), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public int TabSize { get; set; } = 4;

        [Browsable(true), Category("Settings"), Description("The text associated with the control."), Bindable(true), EditorBrowsable(EditorBrowsableState.Always), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public new string Text
        {
            get => base.Text;
            set => base.Text = ConvertTabToSpaces ? value.Replace("\t", new string(' ', TabSize)) : value;
        }

        protected override bool ProcessCmdKey(ref Message Msg, Keys KeyData)
        {
            const int WM_KEYDOWN = 0x100; // https://learn.microsoft.com/en-us/windows/desktop/inputdev/wm-keydown
            const int WM_SYSKEYDOWN = 0x104; // https://learn.microsoft.com/en-us/windows/desktop/inputdev/wm-syskeydown

            if (ConvertTabToSpaces && KeyData == Keys.Tab && (Msg.Msg == WM_KEYDOWN || Msg.Msg == WM_SYSKEYDOWN))
            {
                SelectedText += new string(' ', TabSize);
                return true;
            }
            return base.ProcessCmdKey(ref Msg, KeyData);
        }

        public new void AppendText(string text)
        {
            if (ConvertTabToSpaces)
                text = text.Replace("\t", new string(' ', TabSize));
            base.AppendText(text);
        }

        private void RichTextBox_KeyDown(object sender, KeyEventArgs e)
        {
            if ((e.Shift && e.KeyCode == Keys.Insert) || (e.Control && e.KeyCode == Keys.V))
            {
                SuspendLayout();
                int start = SelectionStart;
                string end = Text.Substring(start);
                Text = Text.Substring(0, start);
                Text += (string)Clipboard.GetData("Text") + end;
                SelectionStart = TextLength - end.Length;
                ResumeLayout();
                e.Handled = true;
            }
        }

    } // class
} // namespace
sɐunıɔןɐqɐp
  • 3,332
  • 15
  • 36
  • 40
  • How is this an improvement on [Kir_Antipov's answer](https://stackoverflow.com/a/52122625/199364)? Regardless, this is NOT how tabs work. A tab is not equivalent to a given number of spaces. Rather, a tab character advances to the next tab stop. Even in a mono-spaced font, suppose there is a tab stop every 8 characters. Type 3 characters, then a tab: FIVE spaces will be advanced. Equally importantly, if its an editable box, and user goes back and adds another 2 characters, now the tab will only be THREE spaces (8-(3+2) = 3). This proposal is broken in both of those cases. – ToolmakerSteve Mar 23 '21 at 23:25
  • 1
    @ToolmakerSteve: I simplified the tab size logic in this class just because I use the RichTextBox mostly for log purposes where tabs are usually at the beginning of each string, so I decided to keep it simple. If you need to support tab behavior like e.g. MSWord does you need to keep an extra copy of the text displayed in the RichTextBox somewhere, and update the whole text every time an inserted text impacts the tabulation. This logic is much more complex, and for obvious performance reasons I did not implement it, since my use case was to construct a log window. – sɐunıɔןɐqɐp Mar 25 '21 at 11:40
  • @ToolmakerSteve: However, I suppose this code could at least give you an insight on how to implement a more complex behavior using the events / hacks there. The use of monospaced fonts can also help to simplify your logic a lot. – sɐunıɔןɐqɐp Mar 25 '21 at 11:42
  • thanks, that is a good explanation of how to do a more complete implementation of tabs. – ToolmakerSteve Mar 25 '21 at 21:17