0

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
}
Community
  • 1
  • 1
Zohar Peled
  • 79,642
  • 10
  • 69
  • 121
  • Did you place your controls inside table layout panel? – J.SMTBCJ15 Mar 17 '17 at 11:28
  • No, I've placed them once inside a form, with no other control in there - the tabindex seems to be working backwards. I've placed them inside another container (that also inherits ZControl), the tab seems to be not working at all. table layout panel is something I try to avoid. – Zohar Peled Mar 17 '17 at 11:31
  • Unfortunately, if you want to make this work you'll have to use table layout panel. Caz once i was having the same issue as you i fixed in this way. As with table layout panel you can achieve responsiveness too – J.SMTBCJ15 Mar 17 '17 at 11:34
  • It is the TabIndex property, very subtle. Basic issue is that setting its value does not force the designer to rewrite the InitializeComponent() method. Works if you change more than one property, not if you only change TabIndex. You need to get rid of the OnGetFocus() override as well, probably the one that made you tinker with TabIndex. UserControl already does this. – Hans Passant Mar 19 '17 at 14:48
  • @hans Thanks, I' give ot a try when I'll be at my computer. – Zohar Peled Mar 19 '17 at 16:17
  • @HansPassant: Unfortunatly, even after removing the TabIndex and the OnGotFocus from the wrapping control still I get funny results in the actual project (while the demo works fine). I'll try more testing today. On another note, I will also need to mimic the `PasswordChar` property (that doesn't exists in a RichTextBox). Any leads on how to do that? – Zohar Peled Mar 20 '17 at 05:16

2 Answers2

4

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.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • Well, that's strange. The only reason I've added this was to try and handle this strange behavior in the first place. However, on a tester project seems like your answer is correct, while on the live project I still have problems with tabs... However, it does seem to be a major step in the right direction and probably is the correct answer to this specific question, so you'll get the bounty when possible. (22 hours from now) – Zohar Peled Mar 19 '17 at 13:11
0

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

  • Thanks for you answer but unfortunatlly it's wrong. I've tried multiple scenarios including just adding 3 textboxes on an emtpy form. The point of this post is that the TabIndex property dosen't work as expected... – Zohar Peled Mar 17 '17 at 11:50