|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
IntroductionIn this first article of the series, I will show you how to drag, resize and rotate any content on a Static Designer ItemLet's start with a simple diagram: <Canvas>
<Ellipse Fill="Blue" Width="130" Height="130" Canvas.Top="100" Canvas.Left="100"/>
</Canvas>
This may not be very useful in terms of a designer application, but it is still a good starting point. We use a <ContentControl Width="130" Height="130" Canvas.Top="100" Canvas.Left="100">
<Ellipse Fill="Blue"/>
</ContentControl>
Next we create a template for the <ControlTemplate x:Key="StaticDesignerItem" TargetType="ContentControl">
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
</ControlTemplate>
<ContentControl Name="DesignerItem" Width="130" Height="130"
Canvas.Top="100" Canvas.Left="100"
Template="{StaticResource StaticDesignerItem}">
<Ellipse Fill="Blue"/>
</ContentControl>
This template doesn't bring us any benefit at the moment, but in the next chapters we will modify the template such that we can drag, resize and rotate any content on the canvas. That is important to grasp: all we need to drag, resize and rotate items on a canvas will be located in the template! By the way, from now on, the Draggable Designer ItemThere exists a control in WPF about which the MSDN documentation says that it, " ...represents a control that lets the user drag and resize controls." That seems to be the perfect candidate for our job. Its name is using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace DragAndResizeControl
{
public class DragThumb : Thumb
{
public DragThumb()
{
base.DragDelta += new DragDeltaEventHandler(DragThumb_DragDelta);
}
void DragThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
ContentControl item = this.DataContext as ContentControl;
if (item != null)
{
double left = Canvas.GetLeft(item);
double top = Canvas.GetTop(item);
Canvas.SetLeft(item, left + e.HorizontalChange);
Canvas.SetTop(item, top + e.VerticalChange);
}
}
}
}
The <ControlTemplate x:Key="DragableDesignerItem" TargetType="ContentControl">
<Grid>
<s:DragThumb DataContext=
"{Binding RelativeSource= {RelativeSource TemplatedParent}}"/>
<ContentPresenter Content="{TemplateBinding
ContentControl.Content}"/>
</Grid>
</ControlTemplate>
<Canvas>
<ContentControl Name="DesignerItem"
Template="{StaticResource DragableDesignerItem}"
Width="130" Height="130" Canvas.Top="100" Canvas.Left="100">
<Ellipse Fill="Blue" />
</ContentControl>
</Canvas>
Now you see where the
What you see behind the ellipses is the default <ControlTemplate x:Key="DragThumbTemplate" TargetType="{x:Type s:DragThumb}">
<Rectangle Fill="Transparent"/>
</ControlTemplate>
<ControlTemplate x:Key="DragableDesignerItem" TargetType="ContentControl">
<Grid>
<s:DragThumb Template="{StaticResource DragThumbTemplate}" Cursor="SizeAll"
DataContext=
"{Binding RelativeSource= {RelativeSource TemplatedParent}}"/>
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
</Grid>
</ControlTemplate>
<Canvas>
<ContentControl Name="DesignerItem" Template="{StaticResource DragableDesignerItem}"
Width="130" Height="130" Canvas.Top="100" Canvas.Left="100">
<Ellipse Fill="Blue" />
</ContentControl>
</Canvas>
Attention: We can start dragging the item only in that area of our <Ellipse Fill="Blue" IsHitTestVisible="False"/>
In the coming article, I will provide you with a more elegant solution for this issue. Now we take the next step, "What does it take to resize the Resizable Designer ItemYou remember that the MSDN documentation promised that the <ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="Control">
<Grid>
<Thumb Height="3" Cursor="SizeNS" Margin="0 -4 0 0"
VerticalAlignment="Top" HorizontalAlignment="Stretch"/>
<Thumb Width="3" Cursor="SizeWE" Margin="-4 0 0 0"
VerticalAlignment="Stretch" HorizontalAlignment="Left"/>
<Thumb Width="3" Cursor="SizeWE" Margin="0 0 -4 0"
VerticalAlignment="Stretch" HorizontalAlignment="Right"/>
<Thumb Height="3" Cursor="SizeNS" Margin="0 0 0 -4"
VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/>
<Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="-6 -6 0 0"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="0 -6 -6 0"
VerticalAlignment="Top" HorizontalAlignment="Right"/>
<Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="-6 0 0 -6"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="0 0 -6 -6"
VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ResizeableDesignerItem" TargetType="ContentControl">
<Grid>
<Control Template="{StaticResource ResizeDecoratorTemplate}"
DataContext="{Binding RelativeSource= {RelativeSource TemplatedParent}}"/>
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
</Grid>
</ControlTemplate>
<Canvas>
<ContentControl Name="DesignerItem" Template="{StaticResource ResizeableDesignerItem}"
Width="130" Height="130" Canvas.Top="100" Canvas.Left="100">
<Ellipse Fill="Blue" IsHitTestVisible="False"/>
</ContentControl>
</Canvas>
We have created a new template that consists of a
Amazing, isn't it. But so far it is only a fake, because there is no event handler that handles the using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace DragAndResizeControl
{
public class ResizeThumb : Thumb
{
private ResizerPosition position;
public ResizerPosition Position
{
get { return position; }
set { position = value; }
}
public ResizeThumb()
{
base.DragDelta += new DragDeltaEventHandler(ResizeThumb_DragDelta);
}
void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
ContentControl item = this.DataContext as ContentControl;
if (item != null)
{
switch (this.Position)
{
case ResizerPosition.Top:
Canvas.SetTop(item, Canvas.GetTop(item) + e.VerticalChange);
item.Height = Math.Max(25, item.ActualHeight - e.VerticalChange);
break;
case ResizerPosition.Bottom:
item.Height = Math.Max(25, item.ActualHeight + e.VerticalChange);
break;
case ResizerPosition.Left:
Canvas.SetLeft(item, Canvas.GetLeft(item) +
e.HorizontalChange);
item.Width = Math.Max(25, item.ActualWidth - e.HorizontalChange);
break;
case ResizerPosition.Right:
item.Width =
Math.Max(25, item.ActualWidth + e.HorizontalChange);
break;
case ResizerPosition.TopLeft:
Canvas.SetTop(item, Canvas.GetTop(item) + e.VerticalChange);
item.Height = Math.Max(25, item.ActualHeight - e.VerticalChange);
Canvas.SetLeft(item, Canvas.GetLeft(item) + e.HorizontalChange);
item.Width =
Math.Max(25, item.ActualWidth - e.HorizontalChange);
break;
case ResizerPosition.TopRight:
Canvas.SetTop(item, Canvas.GetTop(item) + e.VerticalChange);
item.Height = Math.Max(25, item.ActualHeight - e.VerticalChange);
item.Width = Math.Max(25, item.ActualWidth + e.HorizontalChange);
break;
case ResizerPosition.BottomLeft:
item.Height = Math.Max(25, item.ActualHeight + e.VerticalChange);
Canvas.SetLeft(item, Canvas.GetLeft(item) +
e.HorizontalChange);
item.Width = Math.Max(25, item.ActualWidth - e.HorizontalChange);
break;
case ResizerPosition.BottomRight:
item.Height = Math.Max(25, item.ActualHeight + e.VerticalChange);
item.Width = Math.Max(25, item.ActualWidth + e.HorizontalChange);
break;
default:
break;
}
}
e.Handled = true;
}
}
public enum ResizerPosition
{
None,
Top,
Bottom,
Left,
Right,
TopLeft,
TopRight,
BottomLeft,
BottomRight
}
}
The For example: if we drag the thumb in the bottom right corner, we have to update only the Update: meanwhile I have found that it is possible to detect the relative position of a Now let's update the <ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="{x:Type Control}">
<Grid>
<s:ResizeThumb Height="3" Cursor="SizeNS" Margin="0 -4 0 0"
Position="Top" VerticalAlignment="Top"
HorizontalAlignment="Stretch"/>
<s:ResizeThumb Width="3" Cursor="SizeWE" Margin="-4 0 0 0"
Position="Left" VerticalAlignment="Stretch"
HorizontalAlignment="Left"/>
<s:ResizeThumb Width="3" Cursor="SizeWE" Margin="0 0 -4 0"
Position="Right" VerticalAlignment="Stretch"
HorizontalAlignment="Right"/>
<s:ResizeThumb Height="3" Cursor="SizeNS" Margin="0 0 0 -4"
Position="Bottom" VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"/>
<s:ResizeThumb Width="7" Height="7" Cursor="SizeNWSE" Margin="-6 -6 0 0"
Position="TopLeft" VerticalAlignment="Top"
HorizontalAlignment="Left"/>
<s:ResizeThumb Width="7" Height="7" Cursor="SizeNESW" Margin="0 -6 -6 0"
Position="TopRight" VerticalAlignment="Top"
HorizontalAlignment="Right"/>
<s:ResizeThumb Width="7" Height="7" Cursor="SizeNESW" Margin="-6 0 0 -6"
Position="BottomLeft" VerticalAlignment="Bottom"
HorizontalAlignment="Left"/>
<s:ResizeThumb Width="7" Height="7" Cursor="SizeNWSE" Margin="0 0 -6 -6"
Position="BottomRight" VerticalAlignment="Bottom"
HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ResizeableDesignerItem" TargetType="ContentControl">
<Grid>
<Control Template="{StaticResource ResizeDecoratorTemplate}"
DataContext="{Binding RelativeSource= {RelativeSource TemplatedParent}}"/>
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
</Grid>
</ControlTemplate>
<Canvas>
<ContentControl Name="DesignerItem" Template="{StaticResource ResizeableDesignerItem}"
Width="130" Height="130" Canvas.Top="100" Canvas.Left="100">
<Ellipse Fill="Blue" IsHitTestVisible="False"/>
</ContentControl>
</Canvas>
Perfect, now we have a working resize decorator. Rotatable Designer ItemAlthough the MSDN documentation doesn't claim that the <ControlTemplate x:Key="RotateDecoratorTemplate" TargetType="{x:Type Control}">
<Grid>
<s:RotateThumb Margin="-18,-18,0,0" VerticalAlignment="Top"
HorizontalAlignment="Left"/>
<s:RotateThumb Margin="0,-18,-18,0" VerticalAlignment="Top"
HorizontalAlignment="Right">
<s:RotateThumb.RenderTransform>
<RotateTransform Angle="90"/>
</s:RotateThumb.RenderTransform>
</s:RotateThumb>
<s:RotateThumb Margin="0,0,-18,-18" VerticalAlignment="Bottom"
HorizontalAlignment="Right">
<s:RotateThumb.RenderTransform>
<RotateTransform Angle="180" />
</s:RotateThumb.RenderTransform>
</s:RotateThumb>
<s:RotateThumb Margin="-18,0,0,-18" VerticalAlignment="Bottom"
HorizontalAlignment="Left">
<s:RotateThumb.RenderTransform>
<RotateTransform Angle="270" />
</s:RotateThumb.RenderTransform>
</s:RotateThumb>
</Grid>
</ControlTemplate>
Instead of using the default <Style TargetType="{x:Type s:RotateThumb}">
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type s:RotateThumb}">
<Grid Width="30" Height="30">
<Path Fill="#AAD0D0DD" Stretch="Fill"
Data="M 50,100 A 50,50 0 1 1 100,50 H 50 V 100"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here you see how the
Now we have to provide the code for the void RotateThumb_DragStarted(object sender, DragStartedEventArgs e)
{
Canvas canvas = VisualTreeHelper.GetParent(DesignerItem) as Canvas;
if (DesignerItem != null && canvas != null)
{
// the RenderTransformOrigin property of DesignerItem defines
// transformation center relative to its bounds
centerPoint = DesignerItem.TranslatePoint(
new Point(DesignerItem.Width * DesignerItem.RenderTransformOrigin.X,
DesignerItem.Height * DesignerItem.RenderTransformOrigin.Y),
canvas);
// calculate startVector, that is the vector from centerPoint to startPoint
Point startPoint = Mouse.GetPosition(canvas);
startVector = Point.Subtract(startPoint, centerPoint);
// check if the DesignerItem already has a RotateTransform set ...
RotateTransform rotateTransform =
DesignerItem.RenderTransform as RotateTransform;
if (rotateTransform == null)
{
// if not we create one with zero angle
DesignerItem.RenderTransform = new RotateTransform(0);
initialAngle = 0;
}
else
initialAngle = rotateTransform.Angle;
}
}
Whenever a rotate operation is started, we calculate the void RotateThumb_DragDelta(object sender, DragDeltaEventArgs e)
{s = VisualTreeHelper.GetParent(DesignerItem) as Canvas;
if (DesignerItem != null && canvas != null)
{
//we calculate the angle between startVector and dragVector
Point currentPoint = Mouse.GetPosition(canvas);
Vector deltaVector = Point.Subtract(currentPoint, centerPoint);
double angle = Vector.AngleBetween(startVector, deltaVector);
// and update the transformation
RotateTransform rotateTransform =
DesignerItem.RenderTransform as RotateTransform;
rotateTransform.Angle = initialAngle + Math.Round(angle, 0);
}
}
Every time the Drag, Resize and Rotate Designer ItemWhat is left to be done is to merge everything into a style: <Style x:Key="DesignerItemStyle" TargetType="ContentControl">
<Setter Property="MinHeight" Value="50"/>
<Setter Property="MinWidth" Value="50"/>
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<Control Template="{StaticResource RotateDecoratorTemplate}"/>
<s:DragThumb Template="{StaticResource DragThumbTemplate}" Cursor="SizeAll"/>
<Control Template="{StaticResource ResizeDecoratorTemplate}"/>
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>ter>
One has to realize that these few lines of XAML code together with OutlookIn the next article, I will cover the following issues:
History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||