Friday, November 16, 2007

WPF Routed Commands and Focus Scope

Some WPF concepts are not as simple as it might look like. Case in point: Routed Commands. I created the following XAML snippet, and expected the buttons to function out-of-box:

<StackPanel>
    <TextBox/>
    <Button Content="Copy" Command="ApplicationCommands.Copy"/>
    <Button Content="Cut" Command="ApplicationCommands.Cut"/>
    <Button Content="Paste" Command="ApplicationCommands.Paste"/>
 </StackPanel>
It didn't. After a bit of googling, I found the solution here. I.E. The TextBox and Buttons must be put under different containers and the container for the buttons must have FocusManager.IsFocusScope set to true:
<StackPanel>
    <TextBox/>
</StackPanel>
<StackPanel FocusManager.IsFocusScope="True">
    <Button Content="Copy" Command="ApplicationCommands.Copy"/>
    <Button Content="Cut" Command="ApplicationCommands.Cut"/>
    <Button Content="Paste" Command="ApplicationCommands.Paste"/>
</StackPanel>
The post, however, didn't fully explain the connection between Focus Scope and Routed Command. As another bizarre example, when you set the same property on the first StackPanel that contains the TextBox, the buttons fail again.
I couldn't find any official documentation on this behavior. But did manage to dig up this forum reply (the last post on the page) that explains it fairly well. Here is my understanding:
  • By default, Routed Command travels from the source element up the visual tree, from child to parent. In our first example, this involves the button clicked and StackPanel. The text box that contains the command binding never gets a chance to handle the command, hence the buttons were never effective.
  • Setting IsFocusScope creates a separated focus scope that tracks its own focus element. In this case, the routing of command takes a detour: it first bubbles from the button clicked to the StackPanel, which now acts as the root of the newly created focus scope, then it jumps to the focus element of the default visual tree and travel up to its root. Because the focus element in the default visual tree is the text box, it now gets a chance to handle the command.
  • When the stack panel containing the text box is defined as another focus scope, it effectively moves the text box out of the command routing path (because it's no longer in the default visual tree focus scope). Hence the buttons seize to function again.