In this blog post I will describe and share a custom control in Silverlight 2 that you can use to encapsulate other Silverlight-controls. This control will add functionality so that you can move, rotate and scale your Silverlight-controls with your mouse. It also adds a control that will rasie an event (Click-event) when clicked. You can download a sample project with the control at the bottom of the page.![]()
In the image above the red square is the are that will raise a click-event when pressed. The outer squares at the other corners is controls for rotation and the inner squares are controls for scaling.
Problem
Many times when you design an application you do not only want to present information to a user, you also want the user to interact with your program and your controls. Sometimes this interaction involves moving, scaling and rotating an object. I missed a simple control to wrap other controls in that would add this.
How does it work?
A custom control in Silverlight and WPF is a control that contains some functionality and it is look-less. This means that you can change and switch the presentation of a control by changing its template. The base class chosen for this control is ContentControl and this base class is extremely powerful for encapsulating other controls (the content).
The functionality we want in this control is logic for clicking, moving, rotating and scaling. This includes listening to mouse-events of different kinds and the acting on those. What we need is some way of catching these events for the different type of transforms that we want to do.
<Grid x:Name="PART_TranslateControls">
<Rectangle Fill="Transparent" Cursor="Hand" />
</Grid>
<Grid x:Name="PART_ClickControls">
<Rectangle Fill="Red" Stroke="Black" Cursor="Arrow"/>
</Grid>
<Grid x:Name="PART_RotateControls">
<Rectangle Fill="DimGray" Stroke="Black"/>
<Rectangle Fill="DimGray" Stroke="Black"/>
<Rectangle Fill="DimGray" Stroke="Black"/>
</Grid>
<Grid x:Name="PART_ScaleControls">
<Rectangle Fill="LightGray" Stroke="DimGray"/>
<Rectangle Fill="LightGray" Stroke="DimGray"/>
<Rectangle Fill="LightGray" Stroke="DimGray"/>
</Grid>
The rectangles in the snippet above is only the appearance of the control and since we are listening to events from the panels we can set any look and feel of the clickable areas. Also not that we use the PART_ convention for creating custom controls.
In code behind, we add listeners for mousedown events for the different panels and calculates new position, rotation or size when the mouse is moved. The ContentControl contains a TransformGroup with a ScaleTransform, RotateTransform and a TranslateTransform which is the values that we are adjusting.
<TransformGroup>
<ScaleTransform x:Name="PART_Scale"/>
<RotateTransform x:Name="PART_Rotation" />
<TranslateTransform x:Name="PART_Translation" />
</TransformGroup>
We also have dependency properties that we can bind to in this control. The most beautiful solution for this would be to skip using PART_ for the transform and bind to the properties instead. The problem is that you can not bind to ScaleX, Angle, X etc. and therefore we need to get these parts in code-behind and then set the properties manually.
In the sample project below you can see all the mechanisms for this control.
Update 1: The sample code is now updated to Silverlight 2 RTW and I also added an sample of a transformable TextBox and functionality for ZIndex.
Update 2: The sample code has been updated. Hal9000Lives pointed out that the calculations for the rotation used the upper left corner as its center point. This has now been changed to use the middple point as center.
Code and sample project can be found here.
WPF-version of this control can be found here.