1/21/08

How to change size of WPF controls at runtime

This sample code uses mouse events to change the size UI elements on Canvas. Stretching is only done for usual bottom, right and bottom-right of the control - you can do top and left in a similar fashion. For controls I used 3 rectangles (actually borders containing rectangles). All three controls are identical except for the names. Note, that each has a TranslateTransform in the TransformGroup. The C# code uses LINQ query to get the right transform from the group before applying new coordinates to it for dragging. This sample also includes the changing of the Z Order to maintain the control that is on top.

Check out a short video below XAML code. It shows what compiled XAML looks like and demostrates dragging and resizing.

* * * * * * * * * * * * *
* Here's the XAML code: *

* * * * * * * * * * * * *




<Window x:Class="MoveObjectOnMouseDownUpMove.Window1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Canvas x:Name="canvas" MouseMove="Canvas_MouseMove">

<Border Name="border1" Canvas.Left="123" Canvas.Top="35"
BorderBrush="DarkKhaki" BorderThickness="1" CornerRadius="5" Padding="5"
Height="68" Width="68" MinHeight="10" MinWidth="10"
MouseLeftButtonDown="border_MouseLeftButtonDown"
MouseLeave="border_MouseLeave"
MouseEnter="border_MouseEnter">
<Border.BitmapEffect>
<DropShadowBitmapEffect Color="DarkGray"/>

</Border.BitmapEffect>
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<TranslateTransform/>
</TransformGroup>
</Border.RenderTransform>
<Rectangle x:Name="rect" Fill="DarkKhaki" RadiusX="5" RadiusY="5"/>

</Border>
<Border Name="border2" Canvas.Left="123" Canvas.Top="35"
BorderBrush="DarkKhaki" BorderThickness="1" CornerRadius="5" Padding="5"
Height="68" Width="68" MinHeight="10" MinWidth="10"
MouseLeftButtonDown="border_MouseLeftButtonDown"
MouseLeave="border_MouseLeave"
MouseEnter="border_MouseEnter">
<Border.BitmapEffect>
<DropShadowBitmapEffect Color="DarkGray"/>
</Border.BitmapEffect>
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<TranslateTransform/>
</TransformGroup>
</Border.RenderTransform>
<Rectangle x:Name="rect" Fill="DarkKhaki" RadiusX="5" RadiusY="5"/>

</Border>
<Border Name="border3" Canvas.Left="123" Canvas.Top="35"
BorderBrush="DarkKhaki" BorderThickness="1" CornerRadius="5" Padding="5"
Height="68" Width="68" MinHeight="10" MinWidth="10"
MouseLeftButtonDown="border_MouseLeftButtonDown"
MouseLeave="border_MouseLeave"
MouseEnter="border_MouseEnter">
<Border.BitmapEffect>
<DropShadowBitmapEffect Color="DarkGray"/>
</Border.BitmapEffect>
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<TranslateTransform/>
</TransformGroup>
</Border.RenderTransform>
<Rectangle x:Name="rect" Fill="DarkKhaki" RadiusX="5" RadiusY="5"/>
</Border>
</Canvas>
</Window>

When you compile XAML Code, it will look the image below. Before starting to go into C# file to add logic to the events created in XAML, check out the video to see the dragging, resizing and maintaining of the z Order.






* * * * * * * * * * * * * * * * * * *
Here's the C# code
* * * * * * * * * * * * * * * * * * *





public partial class Window1 : Window
{
private Element current = new Element();
public Window1()
{
InitializeComponent();
}


protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
this.current.X = Mouse.GetPosition(this.canvas).X;
this.current.Y = Mouse.GetPosition(this.canvas).Y;

if(this.current.InputElement != null)
this.current.InputElement.CaptureMouse();

if (!this.current.IsStretching)
this.current.IsDragging = true;
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);

if (this.current.InputElement != null)
this.current.InputElement.ReleaseMouseCapture();

this.Cursor = Cursors.Arrow;
}

private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if ( e.LeftButton == MouseButtonState.Pressed &&
current.InputElement != null)
{
//increment z-Order and pass it to the current element,
//so that it stays on top of all other elements

((Border)this.current.InputElement).SetValue(Canvas.ZIndexProperty, this.current.ZIndex++);

if (this.current.IsDragging)
Drag(sender);

if (this.current.IsStretching)
Stretch(sender);
}
}

private void border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//capture the last highest z index before pointing to new current element
int newZIndex = (int)((Border)sender).GetValue(Canvas.ZIndexProperty);
this.current.ZIndex = newZIndex > this.current.ZIndex ? newZIndex : this.current.ZIndex;

//capture the new current element
this.current.InputElement = (IInputElement)sender;
}
private void border_MouseLeave(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
return;

// get coordinates
Border border = (Border)sender;
var rightLimit = border.ActualWidth - border.Padding.Right;
var bottomLimit = border.ActualHeight - border.Padding.Bottom;
var x = Mouse.GetPosition((IInputElement)sender).X;
var y = Mouse.GetPosition((IInputElement)sender).Y;

// figure out stretching directions - only to Right, Bottom
bool stretchRight = (x >= rightLimit && x < border.ActualWidth) ? true : false;
bool stretchBottom = (y >= bottomLimit && y < border.ActualHeight) ? true : false;

// update current element
this.current.InputElement = (IInputElement)sender;
this.current.X = x;
this.current.Y = y;
this.current.IsStretching = true;

//set cursor to show stretch direction
if (stretchRight && stretchBottom)
{
this.Cursor = Cursors.SizeNWSE;
return;
}
else if (stretchRight && !stretchBottom)
{
this.Cursor = Cursors.SizeWE;
return;
}
else if (stretchBottom && !stretchRight)
{
this.Cursor = Cursors.SizeNS;
return;
}
else //no stretch
{
this.Cursor = Cursors.Arrow;
this.current.IsStretching = false;
}
}
private void border_MouseEnter(object sender, MouseEventArgs e)
{
Border border = (Border)sender;

var rightLimit = border.ActualWidth - border.Padding.Right;
var bottomLimit = border.ActualHeight - border.Padding.Bottom;

var x = Mouse.GetPosition((IInputElement)sender).X;
var y = Mouse.GetPosition((IInputElement)sender).Y;

if (x < rightLimit && y < bottomLimit)
this.Cursor = Cursors.Arrow;
}

private void Drag(object sender)
{
this.Cursor = Cursors.Hand;

// Retrieve the current position of the mouse.
var newX = Mouse.GetPosition((IInputElement)sender).X;
var newY = Mouse.GetPosition((IInputElement)sender).Y;

// Reset the location of the object (add to sender's renderTransform newPosition minus currentElement's position
var transformGroup = ((UIElement)this.current.InputElement).RenderTransform as TransformGroup;
if (transformGroup == null)
return;

var translateTransforms = from transform in transformGroup.Children
where transform.GetType().Name == "TranslateTransform"
select transform;

foreach (TranslateTransform tt in translateTransforms)
{
tt.X += newX - current.X;
tt.Y += newY - current.Y;
}

// Update the beginning position of the mouse
current.X = newX;
current.Y = newY;
}
private void Stretch(object sender)
{

// Retrieve the current position of the mouse.
var mousePosX = Mouse.GetPosition((IInputElement)sender).X;
var mousePosY = Mouse.GetPosition((IInputElement)sender).Y;


//get coordinates
Border border = (Border)this.current.InputElement;
var xDiff = mousePosX - this.current.X;
var yDiff = mousePosY - this.current.Y;
var width = ((Border)this.current.InputElement).Width;
var heigth = ((Border)this.current.InputElement).Height;


//make sure not to resize to negative width or heigth
xDiff = (border.Width + xDiff) > border.MinWidth ? xDiff : border.MinWidth;
yDiff = (border.Height + yDiff) > border.MinHeight ? yDiff : border.MinHeight;


// stretchRight && stretchBottom ?
if (this.Cursor == Cursors.SizeNWSE)
{
((Border)this.current.InputElement).Width += xDiff;
((Border)this.current.InputElement).Height += yDiff;
}
// stretchRight ?
else if (this.Cursor == Cursors.SizeWE)
((Border)this.current.InputElement).Width += xDiff;

// stretchBottom ?
else if (this.Cursor == Cursors.SizeNS)
((Border)this.current.InputElement).Height += yDiff;

//no stretch
else
{
this.Cursor = Cursors.Arrow;
this.current.IsStretching = false;
}

// update current coordinates with the latest postion of the mouse
this.current.X = mousePosX;
this.current.Y = mousePosY;
}
}



public class Element
{
#region Fields
private bool isDragging = false;
private bool isStretching = false;
private bool stretchLeft = false;
private bool stretchRight = false;
private IInputElement inputElement = null;
private double x, y = 0;
private int zIndex
= 0;
#endregion

#region Constructor
public Element(){}
#endregion

#region Properties
public IInputElement InputElement
{
get { return this.inputElement; }
set
{
this.inputElement = value;
this.isDragging = false;
this.isStretching = false;
}
}
public double X
{
get { return this.x; }
set { this.x = value; }
}
public double Y
{
get { return this.y; }
set { this.y = value; }
}
public int ZIndex
{
get { return this.zIndex; }
set { this.zIndex = value; }
}
public bool IsDragging
{
get { return this.isDragging; }
set
{
this.isDragging = value;
this.isStretching = !this.isDragging;
}
}
public bool IsStretching
{
get { return this.isStretching; }
set
{
this.isStretching = value;
this.IsDragging = !this.isStretching;
}
}
public bool StretchLeft
{
get { return this.stretchLeft; }
set { this.stretchLeft = value; this.stretchRight = !this.stretchLeft; }
}
public bool StretchRight
{
get { return this.stretchRight; }
set { this.stretchRight = value; this.stretchLeft = !this.stretchRight; }
}
#endregion
}
}
_________________________________________________________________
end of post


5 comments:

  1. Anonymous1/06/2010

    This is exactly what i was looking for ! Thank you very much...

    ReplyDelete
  2. Hi,
    Article is excellent.
    Can we know the coordinates of line control if it is resized

    ReplyDelete
  3. sure, similar logic - onMouseDown get your starting X and Y coordinates, onMouseUp get your ending X and Y, then take the difference. Or you can keep updating coordinates OnDrag event

    ReplyDelete
  4. Anonymous5/26/2011

    This works fine.
    But Whenever I put Canvas within a ScrollViewer
    [i.e., ...]
    Drag Resize is not working properly.

    Please help.

    Thanks

    ReplyDelete
  5. Anonymous12/21/2012

    This works fine.

    I have problems trying to create the borders on the fly, the drag event is not working.

    Please help.

    Thanks

    ReplyDelete