Zohar Peled
Zohar Peled

Reputation: 82474

Problems with user controls and Tab order

I've created a project with multiple user controls to support transparency, gradient and themes for winforms. I was looking for a way to create a replacement for textbox, since winforms textboxes are not using the regular OnPaint and OnPaintBackground that other winforms control use, and sure enough, I've found something I can work with right here on stackoverflow. Brian's comment gave me the solution - Wrapping a transparent RichTextBox inside my own control.

However, this posed a new problem that I can't figure out how to solve - The TabIndex property dosn't operate as expected.

With normal textboxes, when you have multiple textboxes and each one have a different tab index, the focus goes from one textbox to the other in the order specified by the tab index. In my case, it doesn't. Instead, it's unpredictable. I've tried multiple forms with different layouts and controls on them, but I can't seem to find any predictable pattern of behavior that would suggest the problem.

Here is the relevant control's code (the parent, ZControl inherits UserControl if that matters):

/// <summary>
/// A stylable textbox. 
/// <Remarks>
/// The solution for writing a stylable textbox was inspired by this SO post and Brian's comment:
/// https://stackoverflow.com/a/4360341/3094533
/// </Remarks>
/// </summary>
[DefaultEvent("TextChanged")]
public partial class ZTextBox : ZControl
{
    #region ctor

    public ZTextBox()
    {
        TextBox = new TransparentRichTextBox();
        TextBox.BackColor = Color.Transparent;
        TextBox.BorderStyle = BorderStyle.None;
        TextBox.Multiline = false;
        TextBox.TextChanged += TextBox_TextChanged;
        TextBox.TabStop = true;
        TextBox.AcceptsTab = false;
        InitializeComponent();
        AdjustTextBoxRectangle();
        this.Controls.Add(TextBox);
        this.RoundedCorners.PropertyChanged += RoundedCorners_PropertyChanged;
    }

    #endregion ctor

    #region properties 

    private TransparentRichTextBox TextBox { get; }

    public override string Text
    {
        get
        {
            return TextBox.Text;
        }

        set
        {
            TextBox.Text = value;
        }
    }

    [DefaultValue(false)]
    public bool Multiline
    {
        get
        {
            return this.TextBox.Multiline;
        }
        set
        {
            this.TextBox.Multiline = value;
        }
    }

    public override Font Font
    {
        get
        {
            return base.Font;
        }

        set
        {
            if (base.Font != value)
            {
                base.Font = value;
                if (TextBox != null)
                {
                    TextBox.Font = value;
                }
            }
        }
    }

    public new int TabIndex
    {
        get
        {
            return this.TextBox.TabIndex;
        }
        set
        {
            this.TextBox.TabIndex = value;
        }
    }

    #region hidden properties

    [
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        Browsable(false),
        EditorBrowsable(EditorBrowsableState.Never)
    ]
    public override Color ForeColor
    {
        get
        {
            return TextBox.ForeColor;
        }

        set
        {
            TextBox.ForeColor = value;
        }
    }

    [
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        Browsable(false),
        EditorBrowsable(EditorBrowsableState.Never)
    ]
    public override ContentAlignment TextAlign
    {
        get
        {
            return base.TextAlign;
        }

        set
        {
            base.TextAlign = value;
        }
    }

    [
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        Browsable(false),
        EditorBrowsable(EditorBrowsableState.Never)
    ]
    public override Point TextLocationOffset
    {
        get
        {
            return base.TextLocationOffset;
        }

        set
        {
            base.TextLocationOffset = value;
        }
    }

    #endregion hidden properties

    #endregion properties 

    #region methods

    protected override void OnGotFocus(EventArgs e)
    {
        base.OnGotFocus(e);
        TextBox.Focus();
    }

    protected override void DrawText(Graphics graphics, string text, ContentAlignment textAlign, Point locationOffset, Size stringSize)
    {
        // Do nothing - The transparent rich textbox is responsible for drawing the text...
    }

    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        AdjustTextBoxRectangle();
    }

    private void AdjustTextBoxRectangle()
    {
        var corners = this.RoundedCorners.Corners;
        var leftAdjustment = ((corners & RoundedEdges.TopLeft) == RoundedEdges.TopLeft || (corners & RoundedEdges.BottomLeft) == RoundedEdges.BottomLeft) ? this.RoundedCorners.ArcSize / 2 : 0;
        var rightAdjustment = ((corners & RoundedEdges.TopRight) == RoundedEdges.TopRight || (corners & RoundedEdges.BottomRight) == RoundedEdges.BottomRight) ? this.RoundedCorners.ArcSize / 2 : 0;

        TextBox.Top = 0;
        TextBox.Left = leftAdjustment;
        TextBox.Width = this.Width - leftAdjustment - rightAdjustment;
        TextBox.Height = this.Height;
    }

    #endregion methods

    #region event handlers

    private void RoundedCorners_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        AdjustTextBoxRectangle();
    }

    private void TextBox_TextChanged(object sender, EventArgs e)
    {
        OnTextChanged(e);
    }

    #endregion event handlers

    #region private classes 

    private class TransparentRichTextBox : RichTextBox
    {
        public TransparentRichTextBox()
        {
            this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            this.SetStyle(ControlStyles.Opaque, true);
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
        }

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams parms = base.CreateParams;
                parms.ExStyle |= 0x20;  // Turn on WS_EX_TRANSPARENT
                return parms;
            }
        }
    }

    #endregion private classes 
}

And the designer code, if that's relevant:

partial class ZTextBox
{
    /// <summary> 
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary> 
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Component Designer generated code

    /// <summary> 
    /// Required method for Designer support - do not modify 
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.SuspendLayout();
        // 
        // ZTextBox
        // 
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
        this.Name = "ZTextBox";
        this.RoundedCorners.ArcSize = 50;
        this.RoundedCorners.Corners = Zohar.UserControls.RoundedEdges.None;
        this.Size = new System.Drawing.Size(100, 20);
        this.Style.DisabledStyle.BackColor = System.Drawing.Color.Empty;
        this.Style.DisabledStyle.BackgroundImage = null;
        this.Style.DisabledStyle.BorderColor = System.Drawing.Color.Empty;
        this.Style.DisabledStyle.ForeColor = System.Drawing.Color.Empty;
        this.Style.DisabledStyle.Gradient.Angle = 0F;
        this.Style.DisabledStyle.Gradient.BackColor = System.Drawing.Color.Empty;
        this.Style.DisabledStyle.Image = null;
        this.Style.DisabledStyle.Name = null;
        this.Style.EnabledStyle.BackColor = System.Drawing.Color.Empty;
        this.Style.EnabledStyle.BackgroundImage = null;
        this.Style.EnabledStyle.BorderColor = System.Drawing.Color.Empty;
        this.Style.EnabledStyle.ForeColor = System.Drawing.Color.Empty;
        this.Style.EnabledStyle.Gradient.Angle = 0F;
        this.Style.EnabledStyle.Gradient.BackColor = System.Drawing.Color.Empty;
        this.Style.EnabledStyle.Image = null;
        this.Style.EnabledStyle.Name = null;
        this.Style.HoverStyle.BackColor = System.Drawing.Color.Empty;
        this.Style.HoverStyle.BackgroundImage = null;
        this.Style.HoverStyle.BorderColor = System.Drawing.Color.Empty;
        this.Style.HoverStyle.ForeColor = System.Drawing.Color.Empty;
        this.Style.HoverStyle.Gradient.Angle = 0F;
        this.Style.HoverStyle.Gradient.BackColor = System.Drawing.Color.Empty;
        this.Style.HoverStyle.Image = null;
        this.Style.HoverStyle.Name = null;
        this.ResumeLayout(false);

    }

    #endregion
}

Upvotes: 0

Views: 2083

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205629

The issue is caused by the following code:

public new int TabIndex
{
    get
    {
        return this.TextBox.TabIndex;
    }
    set
    {
        this.TextBox.TabIndex = value;
    }
}

You should never do this for UserControl (actually for any control). The documentation for Control.TabIndex property states:

Gets or sets the tab order of the control within its container.

In other words, the control TabIndex property is not global for the form, but scoped to the control container (parent).

The effect is that the form designer code where your control resides will call the shadow TabOrder setter, but the tab navigation handling will simply call the base Control property, leading to undetermined behavior.

Also note that setting the TabIndex of the inner TextBox makes no any sense since it's the only control inside the container (your control). While what you really need is to set the TabIndex of your control inside its container.

With that being said, simply remove the above code and everything will work as expected.

Upvotes: 4

Riless Belayat
Riless Belayat

Reputation: 1

that's probably because you have not added your textbox's in order or maybe you deleted some of them while adding them, anyway you can choose the order on the properties of the controls => TabIndex

Upvotes: 0

Related Questions