WPF:Focus Scope, ICommandSource和命令绑定之间的秘密

目录

 

 

返回目录

1. 简单的Focus Scope内部的焦点切换

首先窗体本身就是一个Focus Scope,用户可以通过FocusManager.IsFocusScope附加属性来定义额外的属性,还有一些控件自己就具备Focus Scope,比如Menu和ToolBar控件。

当在同一个Focus Scope中移动键盘焦点后,Focus Scope的焦点对象和获取键盘焦点的对象都会同时改变。

这个很好理解,我们来测试一下,如下XAML:

<StackPanel GotFocus="GotFocusHandler" 
LostFocus="LostFocusHandler"
GotKeyboardFocus="GotKeyboardFocusHandler"
LostKeyboardFocus="LostKeyboardFocusHandler">
<Button>Mgen</Button>
<TextBox></TextBox>
</StackPanel>

 

事件代码就是输出相应RoutedEventArgs的Source属性:

private void GotFocusHandler(object sender, RoutedEventArgs e)
{
Debug.WriteLine("GotFoucs: " + e.Source);
}

private void LostFocusHandler(object sender, RoutedEventArgs e)
{
Debug.WriteLine("LostFoucs: " + e.Source);
}

private void GotKeyboardFocusHandler(object sender, KeyboardFocusChangedEventArgs e)
{
Debug.WriteLine("GotKeyboardFocus: " + e.Source);
}

private void LostKeyboardFocusHandler(object sender, KeyboardFocusChangedEventArgs e)
{
Debug.WriteLine("LostKeyboardFocus: " + e.Source);
}

 

然后运行程序,整个动作是这样的:先点击一下Button,再点击TextBox,然后再点一下Button。

Output窗口会输出如下信息:

//点击Button
GotFoucs: System.Windows.Controls.Button: Mgen
GotKeyboardFocus: System.Windows.Controls.Button: Mgen

//点击TextBox
LostKeyboardFocus: System.Windows.Controls.Button: Mgen
LostFoucs: System.Windows.Controls.Button: Mgen
GotFoucs: System.Windows.Controls.TextBox
GotKeyboardFocus: System.Windows.Controls.TextBox

//再次点击Button
LostKeyboardFocus: System.Windows.Controls.TextBox
LostFoucs: System.Windows.Controls.TextBox
GotFoucs: System.Windows.Controls.Button: Mgen
GotKeyboardFocus: System.Windows.Controls.Button: Mgen

可以看到,由于TextBox和Button在同一个Focus Scope下,那么Focus Scope内焦点和键盘焦点就会同时改变且应用在同一个对象中。

 

返回目录

2. 多个Focus Scope以及涉及到ICommandSource的焦点切换

而当涉及到多个Focus Scope时,情况就不是这样了,我们来修改下上面的XAML,把Button设置在一个单独的Focus Scope内。

如下代码:

<StackPanel GotFocus="GotFocusHandler" 
LostFocus="LostFocusHandler"
GotKeyboardFocus="GotKeyboardFocusHandler"
LostKeyboardFocus="LostKeyboardFocusHandler">
<Grid FocusManager.IsFocusScope="True">
<Button>Mgen</Button>
</Grid>
<TextBox></TextBox>
</StackPanel>

 

其他事件代码和上面的一样。接下来再次进行刚才的测试:先点击一下Button,再点击TextBox,然后再点一下Button。

Output窗口输出是这样的:

//点击Button
GotFoucs: System.Windows.Controls.Button: Mgen
GotKeyboardFocus: System.Windows.Controls.Button: Mgen
LostKeyboardFocus: System.Windows.Controls.Button: Mgen

//点击TextBox
GotFoucs: System.Windows.Controls.TextBox
GotKeyboardFocus: System.Windows.Controls.TextBox

//再次点击Button
LostKeyboardFocus: System.Windows.Controls.TextBox
GotKeyboardFocus: System.Windows.Controls.Button: Mgen
LostKeyboardFocus: System.Windows.Controls.Button: Mgen
GotKeyboardFocus: System.Windows.Controls.TextBox

结论是这样的:

首先可以看到,在多个Focus Scope移动键盘焦点后,每一个Focus Scope中的焦点元素不会发生LostFocus事件。

另一个结论是Focus Scope的ICommandSource控件(如Button,MenuItem等)并不尝试始终获得键盘焦点,而是当点击完成后,立即放弃键盘焦点,如果之前有元素获得键盘焦点的话,则会把键盘焦点还原为之前的元素。

 

所以定义另一个Focus Scope后,当第二次点击按钮完成后,TextBox会重新获得键盘焦点。

image

 

返回目录

3. Focus Scope, ICommandSource和命令绑定

理解了前两部分,我们再来解释下为什么要这样做。

让我们看一个简单的WPF命令绑定程序,不需要任何C#代码,只需要XAML,程序可以在两个TextBox(或更多)中实现复制和粘贴操作,如下图:

image

 

XAML也很简单:

<StackPanel>
<ToolBar>
<Button Command="Copy">复制</Button>
<Button Command="Paste">粘贴</Button>
</ToolBar>
<TextBox />
<TextBox />
</StackPanel>

 

为什么可以这样?我们的Button仅仅是绑定了WPF命令,而我们有多个TextBox都支持这些命令,但最终运行结果完全可以实现复制和粘贴操作正确得执行在当前获得焦点的TextBox内。

原因就是上面看到的,由于ToolBar自己定义有Focus Scope,所以Button不会尝试获得键盘焦点,Button点击后之前获得键盘焦点的TextBox控件还会继续获得焦点。而当ICommandSource接口的CommandTarget属性没有被设置,它会自动应用在当前键盘焦点的控件上,所以Button绑定的命令会自动验证并执行在获得键盘焦点的TextBox控件内。

 

读者可以试一下把上面的ToolBar换成没有默认Focus Scope的控件,比如StackPanel,如下:

<StackPanel>
<StackPanel>
<Button Command="Copy">复制</Button>
<Button Command="Paste">粘贴</Button>
</StackPanel>
<TextBox />
<TextBox />
</StackPanel>

 

运行程序后你会发现,所有功能都不可使用:

image

 

但如果手动为StackPanel再加入Focus Scope,如下代码:

<StackPanel>
<StackPanel FocusManager.IsFocusScope="True">
<Button Command="Copy">复制</Button>
<Button Command="Paste">粘贴</Button>
</StackPanel>
<TextBox />
<TextBox />
</StackPanel>

 

程序会和之前用ToolBar有一样的功能:

image

 这就是Focus Scope, ICommandSource和命令绑定之间的“秘密”,哈哈。

posted @ 2017-11-27 16:39  jlwg  阅读(250)  评论(0编辑  收藏  举报