小议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的相关代码都是一样的。