Download TextBoxTabStops.zip, last updated 25/05/2019 (56.09 KB)

Download
  • md5: c19ff63a08cba2440f23c149be49025d
  • sha1: e41fa65000e766cd66ed012744340b6fe53df4b4
  • sha256: d3b220e4c5e4753d7b761ee0eb978600bdc14f6a9bbee50117e66a1a052d642c
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using Cyotek.Demo.TextBoxTabStops.Design;

// Setting tab stops in a Windows Forms TextBox control
// https://www.cyotek.com/blog/setting-tab-stops-in-a-windows-forms-textbox-control

// This work is licensed under the Creative Commons Attribution 4.0 International License.
// To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/.

// Found this example useful?
// https://www.paypal.me/cyotek

namespace Cyotek.Demo.TextBoxTabStops
{
  [Designer(typeof(RulerDesigner))]
  internal sealed class Ruler : Control
  {
    #region Private Fields

    private const TextFormatFlags _flags = TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix | TextFormatFlags.SingleLine;

    private int[] _tabStops;

    #endregion Private Fields

    #region Public Constructors

    public Ruler()
    {
      this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
      this.SetStyle(ControlStyles.Selectable, false);
      base.TabStop = false;
      base.ForeColor = SystemColors.ControlDarkDark;
      this.ResetTabStops();
    }

    #endregion Public Constructors

    #region Public Properties

    [DefaultValue(typeof(SystemColors), "ControlDarkDark")]
    public override Color ForeColor
    {
      get { return base.ForeColor; }
      set { base.ForeColor = value; }
    }

    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [DefaultValue(false)]
    public new bool TabStop
    {
      get { return base.TabStop; }
      set { base.TabStop = value; }
    }

    #endregion Public Properties

    #region Protected Properties

    protected override Size DefaultSize
    { get { return new Size(100, 22); } }
    #endregion Protected Properties

    #region Public Methods

    public static int MulDiv(int number, int numerator, int denominator)
    {
      //https://stackoverflow.com/a/25065519/148962

      return (int)((((long)number * numerator) + (denominator >> 1)) / denominator);
    }

    public void ResetTabStops()
    {
      _tabStops = new[] { 32 };
      this.Invalidate();
    }

    public void SetTabStops(int[] tabStops)
    {
      if (tabStops == null || _tabStops.Length == 0)
      {
        throw new ArgumentNullException(nameof(tabStops));
      }

      _tabStops = tabStops;
      this.Invalidate();
    }

    public void SetTabStops(int tabStop)
    {
      _tabStops = new[] { tabStop };
      this.Invalidate();
    }

    #endregion Public Methods

    #region Protected Methods

    protected override void OnPaint(PaintEventArgs e)
    {
      Graphics g;
      Rectangle inner;
      Size size;
      int y;
      int tabWidth;

      base.OnPaint(e);

      // TODO: Need to intercept the scroll events for the underlying textbox
      // in order to calculate an offset. But this is getting beyond the scope
      // of what this demonstration was supposed to be about

      g = e.Graphics;
      size = this.ClientSize;
      y = 7;

      g.Clear(this.BackColor);

      inner = new Rectangle(0, 3, size.Width - 1, size.Height - 10);

      using (Font font = new Font(this.Font.FontFamily, 7F))
      using (Pen pen = new Pen(this.ForeColor))
      {
        g.FillRectangle(SystemBrushes.Window, inner);
        g.DrawRectangle(pen, inner);

        DrawRuler(g, inner, y, font, pen);

        y = inner.Bottom + 2;
        int x;
        int previousTabStop;
        Form owner;

        owner = this.FindForm();
        x = 4;
        tabWidth = 0;
        previousTabStop = 0;

        for (int i = 0; i < _tabStops.Length; i++)
        {
          int mappedTabWidth;

          // TODO: MapDialogRect isn't working for me, probably because
          // the .NET windows aren't being created as dialogs

          //NativeMethods.RECT rect;

          //rect = new NativeMethods.RECT
          //{
          //  left=1,
          //  top=1,
          //  right = _tabStops[i],
          //  bottom = _tabStops[i]
          //};

          //Console.WriteLine(NativeMethods.MapDialogRect(owner.Handle, ref rect));

          // using hardcoded value of 6 to match current font. likely to break on high DPI or if different font used
          mappedTabWidth = MulDiv(_tabStops[i] - previousTabStop, 6, 4);
          previousTabStop = _tabStops[i];
          tabWidth = mappedTabWidth;
          x += tabWidth;

          g.DrawLine(pen, x, y, x, y + 2);
        }

        for (; x < inner.Width; x += tabWidth)
        {
          g.DrawLine(pen, x, y, x, y + 2);
        }
      }
    }

    #endregion Protected Methods

    #region Private Methods

    private void DrawRuler(Graphics g, Rectangle bounds, int y, Font font, Pen pen)
    {
      for (int x = bounds.Left + 101; x < bounds.Width + 100; x += 100)
      {
        string text;
        Size textSize;

        text = ((x - 1) / 100).ToString();
        textSize = TextRenderer.MeasureText(g, text, font, Size.Empty, _flags);

        TextRenderer.DrawText(g, text, font, new Point(x - (textSize.Width / 2), 2 + ((textSize.Height) - bounds.Height) / 2), this.ForeColor, _flags);

        g.DrawLine(pen, x - 75, y + 1, x - 75, y + 3);
        g.DrawLine(pen, x - 50, y, x - 50, y + 5);
        g.DrawLine(pen, x - 25, y + 1, x - 25, y + 3);
      }
    }

    #endregion Private Methods
  }
}

Donate

Donate