XAML is brilliant for creating user interfaces at design time, but if you need to make changes dynamically at runtime, this can sometimes be a problem. To take an example, let's say that you need to allow an Image to move to a different location on screen in response to some user action.
To demonstrate this, we can use an Image which is housed in a Canvas. So the first step is to create a Canvas in a WPF Window, place the Image inside the Canvas and assign a value to the Source property of the Image:
<Window x:Class="Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Image In A Canvas" Height="300" Width="300">
<Canvas x:Name="MainCanvas">
<Image x:Name="MoveableImage" Width="55" Source="questionmark2.jpg" />
</Canvas>
</Window>
We can then have the Image move a few units to the right each time it receives a mouse click. The WPF Image doesn't support the Click event, but MouseDown is available for the same purpose. So the next step is to wire up an event handler for this:
<Image x:Name="MoveableImage" Width="55" Source="questionmark2.jpg"
MouseDown="Image_MouseDown" />
Here's the first pass at the Image_MouseDown event in the code-behind to move the Image 10 units to the right on each mouse down:
Private Sub Image_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
Dim LeftPos As Double = MoveableImage.GetValue(Canvas.LeftProperty)
MoveableImage.SetValue(Canvas.LeftProperty, LeftPos + 10)
End Sub
This seems straightforward enough, doesn't it? The variable named LeftPos reads the value that is currently stored as the attached Canvas.Left property of the Image, then it increments that value by 10. If you were to run this project, you would expect that the Image would jump 10 units to the right when the mouse is clicked on it. But if you try this code, you will find that nothing happens when the Image is clicked.
There's a little WPF Gotcha lurking in the canvas, just waiting to confuse you. When you create this layout, as you can see from the screenshot below, the Image appears in the top left hand corner of the Canvas by default:

You would be forgiven for assuming that the attached Canvas.Left and Canvas.Top values of the Image are set to zero. It's a reasonable assumption to make, especially as you can see the Image in exactly that location. However, it doesn't actually work like that. By default the Left and Top attached properties are in fact set to Double.NaN - Not A Number. This is effectively a null value that is assigned in order to allow the Canvas to make use of its own built-in layout intelligence for more complex scenarios.
The fix is simple - you just have to assign values to the Canvas.Left and Canvas.Top properties in the XAML when you create the Image element, but it's something that isn't obvious. Not only does it mean that the expected movement of the Image doesn't happen, but it will actually cause your application to crash if you try and reset the value in code to move the Image back to the start position (which we will do later).
So, here's the amended XAML for the Image:
<Image x:Name="MoveableImage" Width="55" Source="questionmark2.jpg"
MouseDown="Image_MouseDown"
Canvas.Left="0" Canvas.Top="0" />
With that change in place, the Image will now move when it is clicked.
In this kind of situation, you probably won't want the Image to either get stuck at the right hand side of the Canvas or, possibly even worse, move out of sight. A simple If/Then statement will fix this:
If LeftPos < (MainCanvas.ActualWidth - MoveableImage.Width) Then
MoveableImage.SetValue(Canvas.LeftProperty, LeftPos + 10)
Else
MoveableImage.SetValue(Canvas.LeftProperty, CDbl(0))
End If
As you can see, the first line tests if there is still enough width remaining for the Image to be moved 10 elements to the right and still be visible. If there is, then the 10 unit jump takes place. If not, then the Image is shunted back to far left of the Canvas. Note that this will fail if you haven't set the initial value of the Canvas.Left property as described earlier.
It will also fail if you don't cast the value (in this example, zero) to a Double. This is because the second parameter of the SetValue method is a generic Object Type and the Canvas.Left property requires a Double.
Moving the Image downwards follows the same kind of logic. You set an initial value on the Canvas.Top property then increment this when the MouseDown event fires. Perhaps you want to build in a feature where the left mouse button causes the Image to move left and the right mouse button to move it down.
If you are moving from Windows Forms to WPF, you may expect to find the Button property of the MouseEventArgs. You would then use code along the lines of:
If e.Button = Windows.Forms.MouseButtons.Left Then
' Do Something
End If
WPF is (again) slightly different. It uses the System.Windows.Input.MouseButtonEventArgs class in place of the Windows Forms version and it has a ChangedButton property, not a Button property. So the WPF version of the previous snippet will start with:
If e.ChangedButton = MouseButton.Left Then
The following code snippet combines the use of either the left or right mouse button to move the Image left or down respectively:
Private Sub Image_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
If e.ChangedButton = MouseButton.Left Then
Dim LeftPos As Double = MoveableImage.GetValue(Canvas.LeftProperty)
If LeftPos < (MainCanvas.ActualWidth - MoveableImage.Width) Then
MoveableImage.SetValue(Canvas.LeftProperty, LeftPos + 10)
Else
MoveableImage.SetValue(Canvas.LeftProperty, CDbl(0))
End If
ElseIf e.ChangedButton = MouseButton.Right Then
Dim TopPos As Double = MoveableImage.GetValue(Canvas.TopProperty)
If TopPos < (MainCanvas.ActualHeight - MoveableImage.Width) Then
MoveableImage.SetValue(Canvas.TopProperty, TopPos + 10)
Else
MoveableImage.SetValue(Canvas.TopProperty, CDbl(0))
End If
End If
End Sub
Finally, of course, you can combine the horizontal and vertical movement of the Image - maybe by using the Middle mouse button:
ElseIf e.ChangedButton = MouseButton.Middle Then
MoveableImage.SetValue(Canvas.LeftProperty, MoveableImage.GetValue(Canvas.LeftProperty) + 10)
MoveableImage.SetValue(Canvas.TopProperty, MoveableImage.GetValue(Canvas.TopProperty) + 10)
End If
The above snippet can be expanded to have the tests for reaching the outer edges built in also.
The main issues I particularly wanted to cover in this blog item were the GetValue and SetValue methods, which are a very useful tool in these kind of situations. I also wanted to flag up the various WPF Gotchas that might trip up the unwary WinForms developer. In a follow-up blog, I plan to take this task a step further and look at ways of giving the user several ways of achieving the Image movement - all of which loop back to a single piece of code