小议Focus()方法

      这里说的是System.Windows.Forms.Control.Focus()方法,就是给桌面控件设定焦点的方法。以前也曾无数次使用过该方法,没有怎么注意。偶然间我在MSDN上发现Focus方法的声明是: public bool Focus() 。也就是说Focus()方法设置焦点有可能是会失败的。

      怎么会呢? 我试了一下,在一个Form上放了一个Button和一个TextBox,Tab顺序是Button:0 TextBox:1如图:


    在Form_Load函数里面写下了下面的代码:
        private void Form1_Load(object sender, EventArgs e)
        {
            this.txtTestTextBox.Text = "测试一下";
            this.txtTestTextBox.Focus();
        }

     并给Button添加了一个按钮事件响应函数:
        private void btnTestButton_Click(object sender, EventArgs e)
        {
            System.Windows.Forms.MessageBox.Show("测试按钮被按下");           
        }

      运行程序后,拍空格键。居然发现是弹出的"测试一下"提示框:
       


      显然,这是因为在Form_Load函数中的this.txtTestTextBox.Focus() 返回了false。那么,为什么会返回false呢?这就是我要研究的问题。追赶一下流行,使用了.NET Framework 3.5 源代码调试。
      我先是将断点停在了Form_Load函数的this.txtTestTextBox.Focus();行上。然后再Call Stack窗口上Load System.Windows.Forms.dll的Symbols(就是那个10M大的PDB文件)。
      然后按F11 Step in……
     

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        public bool Focus() {
            Debug.WriteLineIf(Control.FocusTracing.TraceVerbose, "Control::Focus - " + this.Name);
            Debug.WriteLineIf(IntSecurity.SecurityDemand.TraceVerbose, "ModifyFocus Demanded");
            IntSecurity.ModifyFocus.Demand();

            //here, we call our internal method (which form overrides)
            //see comments in FocusInternal
            //
            return FocusInternal();
         }
         代码中执行了两个方法一个是IntSecurity.ModifyFocus.Demand(),另一个是FocusInternal()。IntSecurity.ModifyFocus.Demand()方法是判断是否有相应的权限的,属于安全检查,不去管他。FocusInternal()方法是研究的重点,接着Step in……
       

        [ResourceExposure(ResourceScope.None)]
        internal virtual bool FocusInternal() {
            Debug.WriteLineIf(Control.FocusTracing.TraceVerbose, "Control::FocusInternal - " + this.Name);
            if (CanFocus){
                UnsafeNativeMethods.SetFocus(new HandleRef(this, Handle));
            }
            if (Focused && this.ParentInternal != null) {
                IContainerControl c = this.ParentInternal.GetContainerControlInternal();
 
                if (c != null) {
                    if (c is ContainerControl) {
                        ((ContainerControl)c).SetActiveControlInternal(this);
                    }
                    else {
                        c.ActiveControl = this;
                    }
                }
            }

 
            return Focused;
        }
      从上面的代码看来,Focus()方法返回的false,就是这个FocusInternal方法给出的。这个方法的关键代码是:
UnsafeNativeMethods.SetFocus(new HandleRef(this, Handle));简短截说,UnsafeNativeMethods.SetFocus()方法就是通过Send一个Windows 的Message让控件自己具有焦点的。F10单步执行,结果发现代码执行路径居然跳过了这行代码,也就是说,这个消息根本就没有发出去!为啥呢?从代码上看只有一种可能就是CanFocus的值是false。
      那CanFocus是又是什么呢?重新来过,执行到if(CanFocus)一行时 F11 Step in……
              public bool CanFocus {
            [ResourceExposure(ResourceScope.None)]
            get {
                if (!IsHandleCreated) {
                    return false;
                }
                bool visible = SafeNativeMethods.IsWindowVisible(new HandleRef(window, Handle));
                bool enabled = SafeNativeMethods.IsWindowEnabled(new HandleRef(window, Handle));
                return (visible && enabled);
            }
        } 

        哈哈,原来是一个可访问的公共属性,从代码中我们可以看到决定一个控件是否可以被Focus的条件有两个:一个是控件可见(bool visible = SafeNativeMethods.IsWindowVisible(new HandleRef(window, Handle));),另一个是控件是可用的(bool enabled = SafeNativeMethods.IsWindowEnabled(new HandleRef(window, Handle)); )。只有这两个条件同时具备时,才能支持控件设置焦点。
       那么为什么Form_Load函数里面执行Focus()方法会失败呢?控件的Enable属性为true是肯定的,因为我从没有修改过控件的Enable属性,只有Visible属性有可能是false。也就是说在Form_Load方法在返回之前,程序的界面还没有显示呢。我们再做一个实验:
       在按钮的事件响应函数里面填上this.txtTestTextBox.Focus();代码,测试一下。
       编译,运行,此时焦点在Button上,拍下空格键,弹出MessageBox,确定之后,发现焦点已经转移到TextBox上了,如下图:
      
       这次是设置成功了。那么我们能否在Form_Load中也设置成功呢?肯定能啊,只要在调用Focus方法前让控件编程Visible就可以了。我们可以在Form_Load方法中加入一行this.Show();代码,如下:
        private void Form1_Load(object sender, EventArgs e)
        {
            this.txtTestTextBox.Text = "测试一下";
            this.Show();
            this.txtTestTextBox.Focus();
        }

        这下界面显示出来时,焦点就在TextBox上了。说了这么多,其实就明白了一件事儿,一个控件要具有焦点,需要两个条件:一个是控件是可见的,另一个是控件是Enable状态的。我用Reflector看了一下,.NET 1.1、2.0、3.0、3.5的相关代码都是一样的。

原文地址:https://www.cnblogs.com/michaellee/p/1053366.html