tags:

views:

29

answers:

2

I have a Control inside a Canvas and I want to be able to move it using the arrow keys. For the sake of trying things out, I created the following class, which does what I want.

<Window x:Class="DiagramDesigner.CanvasControlArrowKeyTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="CanvasControlArrowKeyTest" Height="300" Width="300">
    <Canvas>
        <Canvas.InputBindings>
            <KeyBinding Key="Down" Command="MoveDown" />
            <KeyBinding Key="Up" Command="MoveUp" />
            <KeyBinding Key="Right" Command="MoveRight" />
            <KeyBinding Key="Left" Command="MoveLeft" />
        </Canvas.InputBindings>
        <Button>
            <Button.CommandBindings>
                <CommandBinding Command="MoveDown" Executed="MoveDown_Executed" />
                <CommandBinding Command="MoveUp" Executed="MoveUp_Executed" />
                <CommandBinding Command="MoveLeft" Executed="MoveLeft_Executed" />
                <CommandBinding Command="MoveRight" Executed="MoveRight_Executed" />
            </Button.CommandBindings>
        </Button>
    </Canvas>
</Window>

Here's a snippet of the code-behind:

private void MoveDown_Executed(object sender, ExecutedRoutedEventArgs e)
{
    var uiElement = (UIElement)sender;
    double value = Canvas.GetTop(uiElement);
    value = Double.IsNaN(value) ? 0 : value;
    value++;
    Canvas.SetTop(uiElement, value < 0 ? 0 : value);
}

This all works fine, but what I really want is a bunch of Buttons with this ability, not just that one. How can I make sure every button has these CommandBindings? If there's an easier way than using CommandBindings, what might that be?

Update: By request, here is another method that doesn't seem to work:

<Window x:Class="DiagramDesigner.CanvasControlArrowKeyTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="CanvasControlArrowKeyTest" Height="300" Width="300">
    <Window.CommandBindings>
        <CommandBinding Command="MoveDown" Executed="MoveDown_Executed" />
        <CommandBinding Command="MoveUp" Executed="MoveUp_Executed" />
        <CommandBinding Command="MoveLeft" Executed="MoveLeft_Executed" />
        <CommandBinding Command="MoveRight" Executed="MoveRight_Executed" />
    </Window.CommandBindings>
    <Window.InputBindings>
        <KeyBinding Key="Down" Command="MoveDown" />
        <KeyBinding Key="Up" Command="MoveUp" />
        <KeyBinding Key="Right" Command="MoveRight" />
        <KeyBinding Key="Left" Command="MoveLeft" />
    </Window.InputBindings>
    <Canvas >
        <Button Width="50" Height="50" />
    </Canvas>
</Window>

C#

private void MoveDown_Executed(object sender, ExecutedRoutedEventArgs e)
{
    var uiElement = (UIElement)e.OriginalSource; // Still doesn't point to the Button
    double value = Canvas.GetTop(uiElement);
    value = Double.IsNaN(value) ? 0 : value;
    value++;
    Canvas.SetTop(uiElement, value < 0 ? 0 : value);
}

Update: I gave up on this approach. I ended up using a different solution for the problem, one that doesn't use commands.

A: 

You should take a look at routed event handlers: http://msdn.microsoft.com/en-us/library/ms742806.aspx

The link above has an example of how to do what you're asking.

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

And the code

private void CommonClickHandler(object sender, RoutedEventArgs e)
{
  FrameworkElement feSource = e.Source as FrameworkElement;
  switch (feSource.Name)
  {
    case "YesButton":
      // do something here ...
      break;
    case "NoButton":
      // do something ...
      break;
    case "CancelButton":
      // do something ...
      break;
  }
  e.Handled=true;
}
Michael Meadows
This sounds promising, only there is no event for the commands I am using (that is, Button has a Click event, but there isn't a Button.MoveUp, Button.MoveDown, etc). I'll have to read up on this to see if I can get it to work.
Pat
I guess a routed event is really the solution I went with: http://stackoverflow.com/questions/3992278/move-a-control-inside-a-canvas-using-directional-keys/3998438#3998438
Pat
A: 

If you create the commandbindings in the top level object (Window in this case), you'd be able to reuse them for any children of the Window.

<Window x:Class="TesterApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:data="clr-namespace:TesterApp"
        x:Name="TheMainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
    </Window.Resources>
    <Window.InputBindings>
        <KeyBinding Key="A" Command="MoveDown" />
    </Window.InputBindings>
    <Window.CommandBindings>
        <CommandBinding Command="MoveDown" Executed="MoveDown_Executed" />
    </Window.CommandBindings>
    <Grid>
        <Button Height="30" Width="80" Content="Click" Command="MoveDown" />
    </Grid>
</Window>
mdm20
The problem is that the `sender` for the event is not the Button itself, but the high-level control with the CommandBinding. I need a reference to the Button itself so I can move it using Canvas.SetTop, etc.
Pat
check the OriginalSource property of the ExecutedRoutedEventArgs parameter.
mdm20
Oh, if only that worked! For the OriginalSource, I still get the Canvas (which is where I've defined the Command- and Input-Bindings) instead of the Button.
Pat
In my sample, I see the button as the originalsource on button clicks and mainwindow from keybindings. Not sure why yours is different.
mdm20
When I move the Bindings to the Window, the Window is my OriginalSource - so no luck there either. I'm guessing it is because the command is not being routed correctly - not sure how to change that, though. All I know is that Button.Click is a RoutedEvent, whereas MoveDown is just a Command - maybe that's why Click is treated differently.
Pat
Can you post your updated xaml.
mdm20
Add this to your button: Command="MoveDown"
mdm20
@mdm20 That isn't good enough. It's easy to set one command, but setting multiple commands (MoveDown, MoveUp, etc) is the whole reason why this is a difficult problem.
Pat