Monday, 21 November 2011

Mouse Handling in WPF

How to get the Absolute Position of the Mouse on the Screen

You can call Mouse.GetPosition(this) on any WPF element. This function returns the relative offset of the mouse to the upper left corner of your control.
To get the absolute screen cordinates, call the PointToScreen() function.
 
  Point absoluteScreenPos = PointToScreen( Mouse.GetPosition( new Point(), this ));
 

How to Solve Execution Problems of RoutedCommands in a WPF ContextMenu

The Problem

I recently run into a problem, with RoutedCommands in a ContextMenu. The problem was, that the commands could not be executed, even if the CommandBinding on the parent window allowed it.
The following example shows the problem with simple window that has a Menu and a ContextMenu on it. Both menus contains a MenuItem with a "Cut" command set.
 
<Window x:Class="RoutedCommandsInPopups.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel x:Name="stack" Background="Transparent">
        <StackPanel.ContextMenu>
            <ContextMenu>
                <MenuItem Header="Cut" Command="Cut" />
            </ContextMenu>
        </StackPanel.ContextMenu>
        <Menu>
            <MenuItem Header="Edit" >
                <MenuItem Header="Cut" Command="Cut" />
            </MenuItem>
        </Menu>
    </StackPanel>
</Window>
 
 
In the codebehind of the Window I added a CommandBinding to handle the "Cut" command.
 
public Window1()
{
    InitializeComponent();
 
    CommandBindings.Add(
        new CommandBinding(ApplicationCommands.Cut, CutExecuted, CanCut));
}
 
private void CutExecuted(object sender, ExecutedRoutedEventArgs e)
{
    MessageBox.Show("Cut Executed");
}
 
private void CanCut(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}
 
 

The Reason

The reason is, that ContextMenus are separate windows with their own VisualTree and LogicalTree.
The reason is that the CommandManager searches for CommandBindings within the current focus scope. If the current focus scope has no command binding, it transfers the focus scope to the parent focus scope. When you startup your application the focus scope is not set. You can check this by calling FocusManager.GetFocusedElement(this) and you will receive null.

The Solution

Set the Logical Focus

The simplest solution is to initially set the logical focus of the parent window that is not null. When the CommandManager searches for the parent focus scope it finds the window and handels the CommandBinding correctly.
 
public Window1()
{
    InitializeComponent();
 
    CommandBindings.Add(
        new CommandBinding(ApplicationCommands.Cut, CutExecuted, CanCut));
 
    // Set the logical focus to the window
    Focus();
}
 
 
...or the same in XAML
 
<Window x:Class="RoutedCommandsInPopups.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    FocusManager.FocusedElement="
        {Binding RelativeSource={x:Static RelativeSource.Self}, Mode=OneTime}>
    ...
</Window>
 
 

Manually bind the CommandTarget

Another solution is to manually bind the CommandTarget to the parent ContextMenu.
 
<MenuItem Header="Cut" Command="Cut" CommandTarget="
          {Binding Path=PlacementTarget, 
          RelativeSource={RelativeSource FindAncestor, 
          AncestorType={x:Type ContextMenu}}}"/>
 
 

No comments:

Post a Comment