So i've started to look at and build upon the MoveResizeRotate project from http://www.codeproject.com/Articles/22952/WPF-Diagram-Designer-Part.
I've added a couple of things:
- The ability to resize using aspect ratio, if you drag a corner thumb.
- The functionality to flip the ContentControl on the x axis, using a ScaleTransform - i.e. ScaleX = -1.
However, now i'm having trouble when it comes to resizing the control. It works fine before being rotated, but when it's rotated, it seems to resize from the middle instead of that corner/side. This is especially noticeable when the control's ScaleX is set to -1.
Here is my code for the ResizeThumb:
namespace ResizeRotateFlip
{
public class ResizeThumb : Thumb
{
private double angle;
private Point transformOrigin;
private ContentControl designerItem;
public ResizeThumb()
{
DragStarted += new DragStartedEventHandler( this.ResizeThumb_DragStarted );
DragDelta += new DragDeltaEventHandler( this.ResizeThumb_DragDelta );
}
private void ResizeThumb_DragStarted( object sender, DragStartedEventArgs e )
{
this.designerItem = DataContext as ContentControl;
if( this.designerItem != null )
{
this.transformOrigin = this.designerItem.RenderTransformOrigin;
RotateTransform rotateTransform = this.designerItem.RenderTransform as RotateTransform;
if( rotateTransform != null )
{
this.angle = rotateTransform.Angle * Math.PI / 180.0;
}
else
{
this.angle = 0;
}
}
}
private void ResizeThumb_DragDelta( object sender, DragDeltaEventArgs e )
{
if( this.designerItem != null )
{
//variables
double deltaVertical = 0, deltaHorizontal = 0;
double newHeight = 0, newWidth = 0;
double startHeight = this.designerItem.Height;
double startWidth = this.designerItem.Width;
//calculate deltas
switch( VerticalAlignment )
{
case System.Windows.VerticalAlignment.Bottom:
deltaVertical = Math.Min( -e.VerticalChange, this.designerItem.ActualHeight - this.designerItem.MinHeight );
break;
case System.Windows.VerticalAlignment.Top:
deltaVertical = Math.Min( e.VerticalChange, this.designerItem.ActualHeight - this.designerItem.MinHeight );
break;
default:
break;
}
switch( HorizontalAlignment )
{
case System.Windows.HorizontalAlignment.Left:
deltaHorizontal = Math.Min( e.HorizontalChange, this.designerItem.ActualWidth - this.designerItem.MinWidth );
break;
case System.Windows.HorizontalAlignment.Right:
deltaHorizontal = Math.Min( -e.HorizontalChange, this.designerItem.ActualWidth - this.designerItem.MinWidth );
break;
default:
break;
}
// resize
if( dragging_from_corner() )
{
newHeight = this.designerItem.Height - deltaVertical;
newWidth = this.designerItem.Width - deltaHorizontal;
if( newHeight >= 0 && newWidth >= 0 )
{
aspect_ratio_resizing( this.designerItem.Height, this.designerItem.Width, newHeight, newWidth );
}
}
else
{
this.designerItem.Height -= deltaVertical;
this.designerItem.Width -= deltaHorizontal;
}
// translate
double verticalChange = startHeight - this.designerItem.Height;
double horizontalChange = startWidth - this.designerItem.Width;
TransformGroup transformGroup = designerItem.RenderTransform as TransformGroup;
ScaleTransform scaleTransform = transformGroup.Children[(int)TransformType.ScaleTransform] as ScaleTransform;
switch( VerticalAlignment )
{
case System.Windows.VerticalAlignment.Bottom:
if( scaleTransform.ScaleY != -1 )
{
Canvas.SetTop( this.designerItem, Canvas.GetTop( this.designerItem ) + ( this.transformOrigin.Y * verticalChange * ( 1 - Math.Cos( -this.angle ) ) ) );
Canvas.SetLeft( this.designerItem, Canvas.GetLeft( this.designerItem ) - verticalChange * this.transformOrigin.Y * Math.Sin( -this.angle ) );
}
else
{
Canvas.SetTop( this.designerItem, Canvas.GetTop( this.designerItem ) + verticalChange * Math.Cos( -this.angle ) + ( this.transformOrigin.Y * verticalChange * ( 1 - Math.Cos( -this.angle ) ) ) );
Canvas.SetLeft( this.designerItem, Canvas.GetLeft( this.designerItem ) + verticalChange * Math.Sin( -this.angle ) - ( this.transformOrigin.Y * verticalChange * Math.Sin( -this.angle ) ) );
}
break;
case System.Windows.VerticalAlignment.Top:
if( scaleTransform.ScaleY != -1 )
{
Canvas.SetTop( this.designerItem, Canvas.GetTop( this.designerItem ) + verticalChange * Math.Cos( -this.angle ) + ( this.transformOrigin.Y * verticalChange * ( 1 - Math.Cos( -this.angle ) ) ) );
Canvas.SetLeft( this.designerItem, Canvas.GetLeft( this.designerItem ) + verticalChange * Math.Sin( -this.angle ) - ( this.transformOrigin.Y * verticalChange * Math.Sin( -this.angle ) ) );
}
else
{
Canvas.SetTop( this.designerItem, Canvas.GetTop( this.designerItem ) + ( this.transformOrigin.Y * verticalChange * ( 1 - Math.Cos( -this.angle ) ) ) );
Canvas.SetLeft( this.designerItem, Canvas.GetLeft( this.designerItem ) - verticalChange * this.transformOrigin.Y * Math.Sin( -this.angle ) );
}
break;
default:
break;
}
switch( HorizontalAlignment )
{
case System.Windows.HorizontalAlignment.Left:
if( scaleTransform.ScaleX != -1 )
{
Canvas.SetTop( this.designerItem, Canvas.GetTop( this.designerItem ) + horizontalChange * Math.Sin( this.angle ) - this.transformOrigin.X * horizontalChange * Math.Sin( this.angle ) );
Canvas.SetLeft( this.designerItem, Canvas.GetLeft( this.designerItem ) + horizontalChange * Math.Cos( this.angle ) + ( this.transformOrigin.X * horizontalChange * ( 1 - Math.Cos( this.angle ) ) ) );
}
else
{
Canvas.SetTop( this.designerItem, Canvas.GetTop( this.designerItem ) - this.transformOrigin.X * horizontalChange * Math.Sin( this.angle ) );
Canvas.SetLeft( this.designerItem, Canvas.GetLeft( this.designerItem ) + ( horizontalChange * this.transformOrigin.X * ( 1 - Math.Cos( this.angle ) ) ) );
}
break;
case System.Windows.HorizontalAlignment.Right:
if( scaleTransform.ScaleX != -1 )
{
Canvas.SetTop( this.designerItem, Canvas.GetTop( this.designerItem ) - this.transformOrigin.X * horizontalChange * Math.Sin( this.angle ) );
Canvas.SetLeft( this.designerItem, Canvas.GetLeft( this.designerItem ) + ( horizontalChange * this.transformOrigin.X * ( 1 - Math.Cos( this.angle ) ) ) );
}
else
{
Canvas.SetTop( this.designerItem, Canvas.GetTop( this.designerItem ) + horizontalChange * Math.Sin( this.angle ) - this.transformOrigin.X * horizontalChange * Math.Sin( this.angle ) );
Canvas.SetLeft( this.designerItem, Canvas.GetLeft( this.designerItem ) + horizontalChange * Math.Cos( this.angle ) + ( this.transformOrigin.X * horizontalChange * ( 1 - Math.Cos( this.angle ) ) ) );
}
break;
default:
break;
}
}
e.Handled = true;
}
private bool dragging_from_corner()
{
if( ( VerticalAlignment == VerticalAlignment.Top || VerticalAlignment == VerticalAlignment.Bottom ) &&
( HorizontalAlignment == HorizontalAlignment.Left || HorizontalAlignment == HorizontalAlignment.Right ) )
{
return true;
}
return false;
}
private void aspect_ratio_resizing( double originalHeight, double originalWidth, double newHeight, double newWidth )
{
double ratioWidth = newWidth / originalWidth;
double ratioHeight = newHeight / originalHeight;
double ratio = Math.Min( ratioWidth, ratioHeight );
if( originalHeight * ratio > this.designerItem.MinHeight && originalWidth * ratio > this.designerItem.MinWidth )
{
this.designerItem.Height = originalHeight * ratio;
this.designerItem.Width = originalWidth * ratio;
}
}
}
}
Here is my code for RotateThumb:
namespace ResizeRotateFlip
{
public class RotateThumb : Thumb
{
private Point centerPoint;
private Vector startVector;
private double initialAngle;
private Canvas designerCanvas;
private ContentControl designerItem;
public RotateThumb()
{
DragDelta += new DragDeltaEventHandler( this.RotateThumb_DragDelta );
DragStarted += new DragStartedEventHandler( this.RotateThumb_DragStarted );
}
private void RotateThumb_DragStarted( object sender, DragStartedEventArgs e )
{
this.designerItem = DataContext as ContentControl;
if( this.designerItem != null )
{
this.designerCanvas = VisualTreeHelper.GetParent( this.designerItem ) as Canvas;
if( this.designerCanvas != null )
{
this.centerPoint = this.designerItem.TranslatePoint(
new Point( this.designerItem.Width * this.designerItem.RenderTransformOrigin.X,
this.designerItem.Height * this.designerItem.RenderTransformOrigin.Y ),
this.designerCanvas );
Point startPoint = Mouse.GetPosition( this.designerCanvas );
this.startVector = Point.Subtract( startPoint, this.centerPoint );
TransformGroup transformGroup = this.designerItem.RenderTransform as TransformGroup;
RotateTransform rotateTransform = transformGroup.Children[(int)TransformType.RotateTransform] as RotateTransform;
if( rotateTransform == null )
{
initialAngle = 0;
}
else
{
this.initialAngle = rotateTransform.Angle;
}
}
}
}
private void RotateThumb_DragDelta( object sender, DragDeltaEventArgs e )
{
if( this.designerItem != null && this.designerCanvas != null )
{
TransformGroup transformGroup = this.designerItem.RenderTransform as TransformGroup;
ScaleTransform scaleTransform = transformGroup.Children[(int)TransformType.ScaleTransform] as ScaleTransform;
double multiplier = scaleTransform.ScaleX * scaleTransform.ScaleY;
Point currentPoint = Mouse.GetPosition( this.designerCanvas );
Vector deltaVector = Point.Subtract( currentPoint, this.centerPoint );
double angle = Vector.AngleBetween( this.startVector, deltaVector );
RotateTransform rotateTransform = transformGroup.Children[(int)TransformType.RotateTransform] as RotateTransform;
rotateTransform.Angle = this.initialAngle + ( Math.Round( angle, 0 ) ) * multiplier;
this.designerItem.InvalidateMeasure();
}
}
}
}
I'm guessing that my calculation for the X and Y changes (in ResizeThumb) aren't correct. And therefore the translation is not applied correctly after the resize. Though i have noticed that my changes to the RotateThumb have an effect on the resize, before having the TransformGroup in the RotateThumb, the control would resize correctly when rotated.
The project i've referenced uses:
case System.Windows.VerticalAlignment.Bottom:
Canvas.SetTop( this.designerItem, Canvas.GetTop( this.designerItem ) + ( this.transformOrigin.Y * verticalChange * ( 1 - Math.Cos( -this.angle ) ) ) );
Canvas.SetLeft( this.designerItem, Canvas.GetLeft( this.designerItem ) - verticalChange * this.transformOrigin.Y * Math.Sin( -this.angle ) );
break;
for calculating the new position of X & Y for instance: But i'm not sure how to calculate this if the control is flipped. - i should note that the reason i use verticalChange here instead of deltaVertical, is that when using deltaHorizontal and deltaVertical the control tends to move around the canvas.
I suppose my question is more the mathematical side, how do i calculate the new position of X & Y, when the control is rotated and flipped?
EDIT One thing i have just noticed is in the DragStarted method of ResizeThumb, i havent accessed the RotateTransform of my TransformGroup.
I have changed:
RotateTransform rotateTransform = this.designerItem.RenderTransform as RotateTransform;
to:
TransformGroup transformGroup = designerItem.RenderTransform as TransformGroup;
RotateTransform rotateTransform = transformGroup.Children[(int)TransformType.RotateTransform] as RotateTransform;
This solves the resizing being wrong when the ScaleX and ScaleY haven't been changed, however i'm still getting resize issues when i've flipped the control.