用户控件和 Tab 键顺序的问题

Problems with user controls and Tab order

我创建了一个包含多个用户控件的项目,以支持 winforms 的透明度、渐变和主题。 我一直在寻找一种方法来创建 textbox 的替代品,因为 winforms 文本框没有使用其他 winforms 控件使用的常规 OnPaintOnPaintBackground,果然,我找到了我可以使用的东西 right here on Whosebug. Brian 的评论给了我解决方案 - 在我自己的控件中包装一个透明的 RichTextBox。

然而,这带来了一个我不知道如何解决的新问题 - TabIndex 属性 没有按预期运行。

对于普通文本框,当您有多个文本框并且每个文本框都有不同的标签索引时,焦点会按照标签索引指定的顺序从一个文本框转到另一个文本框。就我而言,它没有。相反,它是不可预测的。 我尝试了多种具有不同布局和控件的表单,但我似乎无法找到任何可预测的行为模式来暗示问题。

这里是相关控件的代码(如果重要的话,父控件 ZControl 继承 UserControl):

/// <summary>
/// A stylable textbox. 
/// <Remarks>
/// The solution for writing a stylable textbox was inspired by this SO post and Brian's comment:
/// 
/// </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 
}

以及设计器代码(如果相关):

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
}

这可能是因为您没有按顺序添加文本框,或者您在添加文本框时删除了其中一些,无论如何您可以在控件的属性上选择顺序 => TabIndex

问题是由以下代码引起的:

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

你永远不应该为 UserControl 这样做(实际上是为 any 控制)。 Control.TabIndex 属性 的文档指出:

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

换句话说,控件 TabIndex 属性 不是窗体的全局控件,而是控件容器(父级)的作用域。

效果是您的控件所在的表单设计器代码将调用影子 TabOrder setter,但选项卡导航处理将简单地调用基 Control 属性,导致不确定的行为。

另请注意,设置内部 TextBoxTabIndex 没有任何意义,因为它是 container(您的控件)内的唯一控件。虽然您真正需要的是在其容器内设置控件的 TabIndex

话虽如此,只需删除上面的代码,一切都会按预期进行。