FocusScope学习二: 很好的理解FocusScope的工作原理

转载 2012年03月28日 21:58:07

http://www.codeproject.com/Articles/38507/Using-the-WPF-FocusScope

Introduction

Often, it is useful to maintain a separate focus for different parts of the user interface. For example, when you have a tab control with different data entry fields on each page, it makes sense to remember the focus for each individual page. The default WPF behavior is to reset the focus to the first child element whenever the active page changes - highly annoying if you switch tab pages using the Ctrl+Tab shortcut to look at something you entered on another page, and then when you Ctrl+Tab back to the tab page you were editing, you always have to fetch the mouse or press Tab N times to get back to the text box you were editing.
The solution to this problem is to remember the logical focus inside each tab page, and restore keyboard focus to the appropriate control when the page is activated again.

注意,文章的作者并没有给出以上问题的Solution,只不过把TabControl的例子作为一个研究的开头而已,并通过以下的例子来说明FocusScope的底层机制,并实现了自己的Enhanced Focus Scope Attached Dependency Property.


KeyboardNavigationMode.Once - A Solution?

You just set KeyboardNavigation.ControlTabNavigation="Once" on the GroupBox, and it suddenly starts working - as long as you only navigate using the keyboard. But in my usecase, it is a requirement to also restore the previous focus when the GroupBox is clicked with the mouse. Unfortunately, WPF doesn't provide any API for the magic behind KeyboardNavigationMode.Once; it seems to be impossible to programmatically restore focus without using Reflection to access WPF's internals (I hope someone proves me wrong on this).
If you feel adventurous, call KeyboardNavigation.GetActiveElement using Reflection and skip the rest of this article (the example code does this for the leftmost group box).

Focus Scope - A Solution?

Wait - separate logical focus from keyboard focus? WPF already does that!
It seems that we could simply set FocusManager.IsFocusScope="True", and WPF would do the hard work for us. Unfortunately, this has some horrible side effects.

The MSDN thread "A FocusScope Nightmare (Bug?)" captures my initial reaction quite well. (这篇讨论很经典,我会在后面的博文里面转载关键内容)

  • Why does this seemingly innocent change totally cripple WPF routed commands?
  • Did I run into a WPF bug?
  • How do I get out of this nightmare?

This article explains why the focus scopes in WPF work like they do; and it presents a simple solution that makes them work like we want.

What are the Problems with the WPF FocusScope?

  • Routed Commands do not work inside focus scopes.(是指挂接在容器外的事件处理函数,没法处理那些在FocusScope内的CommandSource的命令)
  • It causes other controls to think they still have focus. In the screenshot at the beginning of the article, two text boxes display a caret.
  • Several controls like buttons and checkboxes will move focus somewhere else when pressed.

For What was FocusScope Designed?

Microsoft uses FocusScope in WPF to create a temporary secondary focus. Every ToolBar and Menu in WPF has its own focus scope.

With this knowledge, we can clearly see why we have those problems:

A toolbar button should not execute commands on itself, but on whatever had focus before the toolbar was clicked. To accomplish this, routed commands ignore the focus from focus scopes and use the 'main' logical focus instead. (说的很对,会忽略在Scope内具有焦点Element,而把事件路由的起点设置在具有Main Focus的元素上;这个很好理解,比如Toolbar上的按钮是事件源,TextBox是Main Focus的元素,那么当你点击Toolbar上的按钮是,焦点会被按钮夺走,那么命令还怎么执行呢?所以WPF选择记住被夺走前的焦点元素,并将它作为事件的对象Command Target, 我觉得这也正是FocusScope被引入的原因,而WinFORM和Win32没这个问题,是因为他们根本没有路由这个概念,而且命令的实现永远是你来处理的)
This explains why routed commands don't work inside focus scopes.

Why does the large text box in the test application screenshot still display a caret? I don't know the answer to this - but why shouldn't it? Granted, the text box doesn't have the keyboard focus (the small text box in the WPF focus scope has that); but it still has the main logical focus in the active Window and is the receiver of all routed commands.

Why does the keyboard focus move to the large text box when you tab to the CheckBox in the WPF focus scope and press Space to toggle it?

Well, this is exactly what you expect when you click a menu item or a toolbar: the keyboard focus should return to the main focus. All ButtonBase-derived controls will do this. (我觉的,这个是WPF设计者的无奈,这样设计真的有点over,但是对于Toolbar和MeuItem又不得不这样做,在我看来,在这个方面WPF设计的并不好!)

How Does the WPF FocusScope Work Under the Covers?

If you don't know focus scopes yet: you can turn any control into a focus scope by setting the attached property FocusManager.IsFocusScope to true. The default styles of ToolBar and Menu do this; there isn't any magic involved with those controls.

Each focus scope stores the logical focus in the attached property FocusManager.FocusedElement. When a control receives keyboard focus, WPF will look up its parent focus scope (the nearest parent with IsFocusScope turned on) and assign it to the FocusedElement property, giving the control logical focus within that scope.
(注意,只会设置一个!每个Scope都会有自己的Logic Focus Element,而且互补干扰,一个Scope的焦点元素不会是其他Scope或者是子Scope的元素)

A WPF Window itself is a focus scope, so the main logical focus simply is the FocusedElement property on the Window instance. Routed Commands simply execute on the main focus. (Window 的Focus Elememt就是Main Focus Element,注意可能是null哦,因为Window下面所有的Container都是一个独立的Scope就有可能的!这个并不奇怪)

The Solution (确保Main Focus设置成和子Scope的焦点元素相同。。这个其实违背了上面的Rule,但是就是可以Work,哈哈)

Actually, now that we know what is happening, there is only a single problem to fix: we need to ensure that the main logical focus gets set.

WPF only gives a control the logical focus within the nearest parent focus scope. We will simply give it the logical focus within all parent focus scopes.

But we don't want to break ToolBars and Menus. Instead, we will implement the new focus logic as an Attached Behavior. This allows using our improved focus scope as easily as the existing: t:EnhancedFocusScope.IsEnhancedFocusScope="True".

If we encounter a focus scope that is not of our 'enhanced' kind, we will stop, just like WPF does.

static void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
    IInputElement focusedElement = e.NewFocus;
    for (DependencyObject d = focusedElement as DependencyObject; 
		d != null; d = VisualTreeHelper.GetParent(d)) {
        if (FocusManager.GetIsFocusScope(d)) {
            d.SetValue(FocusManager.FocusedElementProperty, focusedElement);
            if (!(bool)d.GetValue(IsEnhancedFocusScopeProperty)) {
                break;
            }
        }
    }
}

Basically, this converts the focus scope from being 'temporary' (like menus/toolbars) to a permanent focus scope.

Now, all that's left is restoring the focus, e.g. when an empty area on the GroupBox is clicked. This can be easily done using:

IInputElement storedFocus = FocusManager.GetFocusedElement(groupBox);
if (storedFocus != null)
    Keyboard.Focus(storedFocus);

Conclusion

I hope focus scopes are less of a mystery to you after reading this article.

We didn't have to do a lot to get them working, but we still ended up both manually saving and reading the focus.
This raises the question whether we should use the WPF focus scopes at all - we could have simply invented our own kind instead.


QML类型说明-FocusScope

FocusScope ImportStatement:   import QtQuick 2.2 Inherits:      Item   DetailedDescription 在创建可...
  • Vampire_Armand
  • Vampire_Armand
  • 2014年09月09日 15:24
  • 1164

FocusScope学习三: 对FocusScope 的探究与总结

http://social.msdn.microsoft.com/forums/en-US/wpf/thread/f5de6ffc-fa03-4f08-87e9-77bbad752033/ 这个...
  • puncha
  • puncha
  • 2012年03月28日 22:15
  • 1682

QML FocusScope的问题

在Qt4.8.4中,官方有一个叫searchbox的QML使用FocusScope制作的例子. 运行过的应该知道,这个例子实际上有bug存在,顶层的focusscope的focus被置为true时输...
  • yuxiaohen
  • yuxiaohen
  • 2013年05月21日 15:47
  • 3650

FocusScope学习二: 很好的理解FocusScope的工作原理

http://www.codeproject.com/Articles/38507/Using-the-WPF-FocusScope Introduction Often, it is...
  • puncha
  • puncha
  • 2012年03月28日 21:58
  • 2665

qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)

qml学习笔记(二):界面元素基类Item详解(上半场anchors等等)     本学章节笔记主要详解Item元素(上半场主要涉及anchors锚),因为所有可视化的界面元素都继承于Item,熟悉...
  • qq21497936
  • qq21497936
  • 2017年11月12日 23:16
  • 309

GridView的主从表编辑和新增

GridView的主从表编辑和新增 主要有两个页面PastList.aspx  主表列表页面PastView.aspx 主从表编辑页面SqlHelper.cs   数据访问类和一个实体类一个枚举类**...
  • gaotongzhao
  • gaotongzhao
  • 2011年06月19日 18:13
  • 689

QML --- Element --- FocusScope

FocusScope 这个元素顾名思义就是创建焦点范围。 他继承与Item, 因此拥有Item拥有的一切属性。   详细见Keyboard Focus in QML文档...
  • zhangmingxingwei
  • zhangmingxingwei
  • 2013年05月30日 21:43
  • 660

QML事件处理--按键处理

Qt 帮助:Keyboard Focus in QML QML项目获取焦点的属性:focus: true QML项目在Qt传统的键盘焦点模型上天剑了基于作用域的扩展。 1. 按键处理概述 用户...
  • u012419303
  • u012419303
  • 2015年05月04日 10:19
  • 2286

键盘焦点和逻辑焦点(Logic Focus与Keyboard Focus )

键盘焦点和逻辑焦点(Logic Focus与Keyboard Focus ) 键盘焦点和逻辑焦点(Logic Focus与Keyboard Focus ) 1.定义 Keyb...
  • LongtengGensSupreme
  • LongtengGensSupreme
  • 2017年09月28日 14:50
  • 156

QML初级用法

import QtQuick 2.0 Text{ id: label x: 24; y: 24 property int spacePresses: 0 te...
  • u010002704
  • u010002704
  • 2014年10月21日 10:50
  • 1262
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:FocusScope学习二: 很好的理解FocusScope的工作原理
举报原因:
原因补充:

(最多只允许输入30个字)