Sdílet prostřednictvím


WPF Command Scope (Focus Scope)

My friend ran into a problem recently where they wanted to use commands such as Cut, Copy, Paste in buttons, they couldn’t figure out how to bind the buttons CommandTarget property to multiple targets. For those of you not familiar with WPF Commands check out this MSDN article.

When a command is applied to a control such as a button the focus scope of the button or it’s parent container determine if the command will apply to other controls in the Window or Page. By default Menu or ToolBar elements have their own FocusScope so controls with commands on them will work on controls outside that FocusScope.

For example the xaml snippet below shows a DockPanel with a Menu which has a MenuItem that whose Command property is assigned the Copy command. Additionally the StackPanel below the menu has a button whose Command property is also set to Copy.

<DockPanel>

    <Menu DockPanel.Dock="Top">

      <MenuItem Command="Copy"/>

    </Menu>

    <StackPanel DockPanel.Dock="Top">

      <Button Command="Copy" >Copy</Button>

    </StackPanel>

    <StackPanel Name="Group" DockPanel.Dock="Bottom">

      <TextBox Name="MyTextBox1"/>

      <TextBox Name="MyTextBox2"/>

    </StackPanel>

</DockPanel>

When this code is run typing some text and then selecting that text in either TextBox in the last StackPanel will cause the MenuItem to become in enabled because it is bound to the Copy command, but the Button in the StackPanel will remain disabled. The reason for this is because the Button is in the same FocusScope as the TextBox and in WPF this will keep the Command from working with the TextBox.

To solve this problem we can add a CommandTarget property to the Button and bind to the TextBox by name. This is shown in the xaml snippet below. We add CommandTarget ="{Binding ElementName=MyTextBox1}" To the Button which causes the Button to enable when we select text in the first TextBox, but we still have a problem. We want the Copy button to work for all Text elements in our application, not just the single TextBox we have set our CommandTarget property to.

<DockPanel>

    <Menu DockPanel.Dock="Top">

      <MenuItem Command="Copy"/>

    </Menu>

    <StackPanel DockPanel.Dock="Top">

      <Button Command="Copy" CommandTarget="{Binding ElementName=MyTextBox1}">Copy</Button>

    </StackPanel>

    <StackPanel Name="Group" DockPanel.Dock="Bottom">

      <TextBox Name="MyTextBox1"/>

      <TextBox Name="MyTextBox2"/>

    </StackPanel>

</DockPanel>

To solve this problem we are going to use an attached property on a class called FocusManager. The property is called IsFocusScope and is a bool. This property allows us to set the FocusScope for a control. The xaml snippet below uses the attached property on the StackPanel to set the StackPanel to its own FocusScope. This makes it so that we don’t need to set a CommandTarget, because the StackPanel with the button and the TextBoxes are now in different FocusScopes.

<DockPanel>

    <Menu DockPanel.Dock="Top">

      <MenuItem Command="Copy"/>

    </Menu>

    <StackPanel FocusManager.IsFocusScope="True" DockPanel.Dock="Top">

      <Button Command="Copy" >Copy</Button>

    </StackPanel>

    <StackPanel Name="Group" DockPanel.Dock="Bottom">

      <TextBox Name="MyTextBox1"/>

      <TextBox Name="MyTextBox2"/>

    </StackPanel>

 </DockPanel>

Menu and ToolBar both use the FocusManager.IsFocusScope property to set their own FocusScope and if desired you can set that property to false on a Menu or ToolBar to get the reverse behavior. The xaml snippet below does this by setting FocusManager.IsFocusScope="False" on the Menu.

<DockPanel>

    <Menu FocusManager.IsFocusScope="False" DockPanel.Dock="Top">

      <MenuItem Command="Copy"/>

    </Menu>

    <StackPanel FocusManager.IsFocusScope="True" DockPanel.Dock="Top">

      <Button Command="Copy" >Copy</Button>

    </StackPanel>

    <StackPanel Name="Group" DockPanel.Dock="Bottom">

      <TextBox Name="MyTextBox1"/>

      <TextBox Name="MyTextBox2"/>

    </StackPanel>

</DockPanel>

Comments