Pages

Monday, 2 May 2011

WPF: How to change attached property values at run time

Introduction
WPF uses the concept of attached properties to enable child elements to position themselves inside a parent panel.  For instance, child elements housed in a Grid can use the Grid.Row and Grid.Column attached properties to have themselves located in their required location.  Similarly, children of a Canvas can use the Canvas.Top and Canvas.Left attached properties.  In most situations, once the child element has been placed in its desired location, it will probably need to stay there for the lifetime of the window.  But if you did need to move the child element, how can you do this?

In Windows Forms, you simply have to change the value of the relevant property – e.g. set the Top property to a higher or lower value to have the child element move.  But the WPF attached property is more complex than that and so you need a different approach.  Here’s how it works:

Grid Scenario
To demonstrate this technique, let’s take a trivial example, but one that suits our purposes quite well.  We’ll take a scenario where you have a Grid panel that has 5 rows and 5 columns.  There is an Ellipse element located in the top left corner of the grid – that is, in Row 0, Column 0:

Attached1

If the user clicks on the ellipse, that will cause the ellipse to move to a different row or column position.  So, for this example, we’ll monitor the left click event and when it fires we’ll move the ellipse down one row.  Similarly, the right click event will move the ellipse one column across.

Here’s the markup that creates the red ellipse and identifies its click event handlers:

MouseLeftButtonDown="RedDot_MouseLeftButtonDown"
MouseRightButtonDown="RedDot_MouseRightButtonDown"/>

Finding the Attached Property value
The key to this is to use the GetValue method.  The GetValue method will enable you to get the current value of whichever attached property you’re interested in.  It’s slightly more complicated than that though, for two reasons.

Firstly, the GetValue method returns an object.  So, even though in this example for instance you might be expecting an integer value (representing the row or column number) , you’ll get an object.  All you need do, of course, is cast the object to the required type – Integer, in this case.

The second, slightly unusual feature of this technique is that you have to get the value indirectly from the grid, not directly from the ellipse.  As I mentioned at the start, you can’t simply grab the Ellipse’s Top or Column Number or some such property.  What you do is you pass in the name of an appropriate Shared property of the grid element.  As you can see in the example below, to get the current Grid.Row value of the ellipse, you use the RowProperty shared property of the grid type:

Dim topPosition As Integer = CInt(RedDot.GetValue(Grid.RowProperty))

The code above will return the current Grid.Row value of the ellipse as an integer.

Changing the Attached Property value
Again, if you’ve come from a WinForms background, you might think that you could do something like:

topPosition += 1

to shunt the ellipse down one row.  But WPF has a different approach on this too.  This time, you’ll use the SetValue method to change the current value of the ellipse’s Grid.Row position:

RedDot.SetValue(Grid.RowProperty, topPosition + 1)

As you can see, you call the SetValue method on the ellipse and pass in the name of the attached property you want to change, together with the new value.

Putting it all together
So, putting the above code snippets into the event handler, you have:

Private Sub RedDot_MouseLeftButtonDown(sender As System.Object, e As System.Windows.Input.MouseButtonEventArgs)

    Dim topPosition As Integer = CInt(RedDot.GetValue(Grid.RowProperty))
RedDot.SetValue(Grid.RowProperty, topPosition + 1)

End Sub

Then, to add the right mouse button feature:

Private Sub RedDot_MouseRightButtonDown(sender As System.Object, e As System.Windows.Input.MouseButtonEventArgs)

    Dim columnPosition As Integer = CInt(RedDot.GetValue(Grid.ColumnProperty))
RedDot.SetValue(Grid.ColumnProperty, columnPosition + 1)

End Sub

Now, when the user clicks either of the mouse buttons on the ellipse, it will be moved across or down one cell:

attached2

Of course, you’d probably want to build in a test that the ellipse hasn’t reached the right or bottom bounds of the grid and, if it has, reset it back to its start position.  In passing, it’s useful to know though that if you were to increment the row or column number values beyond the bounds of the grid – in other words, if you clicked to the stage where the row or column value was 5, 6, 7, or more – then you won’t get an exception or a crash; instead the ellipse will simply stay in the last row or column.

Summary
It’s interesting to learn how WPF deals with these kinds of situations, often quite differently from the Windows Forms approach. The underlying reason is that (although I’ve accurately described them as Attached properties) the Grid.Row and Grid.Column properties are in fact Dependency properties.   And, as the name implies, the values of dependency properties depend on a number of factors and can (and do) change during the lifetime of the application.  The local value set by the click events demonstrated here is just one way that the position of the ellipse might be changed. 

Posted Apr 13 2011, 10:18 AM by Ged Mead Filed under: XAML, WPF, .NET, VB.NET, Visual Basic.NET, Visual Basic WPF, GetValue method, Attached Property, Dependency Property, SetValue method

View the original article here

0 comments:

Post a Comment

 
Powered by Blogger