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.

video




* * * * * * * * * * * * * * * * * * *
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


How to change the Z order of WPF controls at runtime

It's as simple as this -- keep track of the highest zIndex and before assigning it to the control that needs to have the highest ZIndex - increment it, that way you ensure that the current control has the highest ZIndex and also if there is a set Z order, you are not going to mess it up.

The code would look something like this:


//set
var zIndex = ((YourCurrentControlType)sender).GetValue(Parent.ZIndexProperty);

//get
someControl.SetValue (Parent.ZIndexProperty, zIndex++);

In my previous post I wanted to move WPF controls at runtime on canvas by selecting them with the mouse and dragging them to a new location. The problem was that the dragged control, if overlapped the other control, would appear to be behind it, if its Z order was lower. So I needed to change the Z order of the control that is being dragged.


L O G I C


Current control should have the highest Z order, so introduce an variable that keeps track of the highest Z order and assigns it to the current control that needs it (incrementing it beforehand, to ensure that it has the highest one).



I M P L E M E N T A T I O N

I used the code from my previous post on how to move WPF controls and modified a couple of things:

1. Introduce the z Order variable by adding it as the ZIndex property to the public class Element

  private int zIndex = 0;
public int ZIndex
{
get { return this.zIndex; }
set { this.zIndex = value; }
}

2. add a line of code to function rect1_MouseLeftButtonDown() to capture the current Z order:


private void rect1_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;

}
3.Assign the highest Z order to the current control in Canvas_MouseMove:




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);//shortened, see how to drag in previous post
}
}

And that's it...pretty simple.
_____________________________________________



1/10/08

How to move WPF controls with mouse at runtime

I wanted to be able to drag or move controls around at runtime (using TranslateTransform) - by clicking the mouse on the desired control and moving it to a new location on the parent screen/control. This code sample uses the simplest WPF Elements - a Canvas and a couple of Rectangles.

I don't think this needs much explaination. So just copy/paste and run it. You should be able to see two rectangles on canvas and move them around when mouse is down.



L O G I C


  • Program maintains a variable "current" that keeps track of which control (or UI element) on canvas is under a possible dragging action.
  • Each Rectangle declares RenderTransform's TranslateTransform and subscribes to MouseLeftButtonDown events, where it is assigned to the current element.
  • Canvas subscribes to MouseDown, MouseUp and MouseMove. In MouseDown canvas gets the coordinates of the current control and captures the mouse on it. In MouseUp it releases the current element and the mouse. The MouseMove resets the location of the current element by adjusting the current element's RenderTransform with the new position.


C O D E
/* * * * * * * * * * * * * * * * * * * ** * * * ** * * * * * * * * *


. .xaml file:

* *  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

<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"

MouseDown
="Canvas_MouseDown"

MouseUp="Canvas_MouseUp"

MouseMove="Canvas_MouseMove">

<Rectangle x:Name="rect2" Height="50" Width="50" Fill="Blue"

Canvas.Left="50" Canvas.Top="100"

MouseLeftButtonDown
="rect2_MouseLeftButtonDown">

<Rectangle.RenderTransform>

<TranslateTransform/>

</Rectangle.RenderTransform>

</Rectangle>

<Rectangle x:Name="rect1" Height="50" Width="50" Fill="Red" MouseLeftButtonDown="rect1_MouseLeftButtonDown">

<Rectangle.RenderTransform>

<TranslateTransform />

</Rectangle.RenderTransform>


</Rectangle>

</Canvas>

</Window>

    /* * * * * * * * * * * * * * * * * * * ** * * * ** * * * * * * * * *

    . xaml.cs file:
    * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 
    using System.Windows.Media.Imaging;

    using System.Windows.Navigation;

    using System.Windows.Shapes;



    namespace MoveObjectOnMouseDownUpMove

    {

    public partial class Window1 : Window

    {
       //see the Element class declaration at the bottom, using it for readablity 

    private Element current = new Element();


    public Window1()

    {

    InitializeComponent();

    }



    private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)

    {

    this.current.X = Mouse.GetPosition((IInputElement)sender).X;

    this.current.Y = Mouse.GetPosition((IInputElement)sender).Y;



    // Ensure object receives all mouse events.

    this.current.InputElement.CaptureMouse();

    }



    private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)

    {

    if (this.current.InputElement != null)

    this.current.InputElement.ReleaseMouseCapture();

    }



    private void Canvas_MouseMove(object sender, MouseEventArgs e)

    {

    // if mouse is down when its moving, then it's dragging current

    if (e.LeftButton == MouseButtonState.Pressed)

    this.current.IsDragging = true;



    if (this.current.IsDragging && current.InputElement != null)

    {

    // 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 rt = ((UIElement)this.current.InputElement).RenderTransform;

    var offsetX = rt.Value.OffsetX;

    var offsetY = rt.Value.OffsetY;

    rt.SetValue(TranslateTransform.XProperty, offsetX + newX - current.X);

    rt.SetValue(TranslateTransform.YProperty, offsetY + newY - current.Y);



    // Update position of the mouse

    current.X = newX;

    current.Y = newY;

    }

    }



    private void rect1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

    {

    this.current.InputElement = (IInputElement)sender;

    }

    private void rect2_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

    {

    this.current.InputElement = (IInputElement)sender;

    }

    }

    }
    /* * * * ** * * * * * * * * * * * * * * * * * * * * * * * * * * 
    helper class (don't need it, but makes things easier to read) 
    * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */



    public class Element

    {



    #region Fields

    bool isDragging = false;

    IInputElement inputElement = null;

    double x, y = 0;

    #endregion



    #region Constructor

    public Element() { }

    #endregion



    #region Properties

    public IInputElement InputElement

    {

    get { return this.inputElement; }

    set

    {

    this.inputElement = value;

    /* every time inputElement resets, the draggin stops (you actually don't even need to track it, but it made things easier in the begining, I'll change it next time I get to play with it. */


    this.isDragging = false;

    }

    }

    public double X

    {

    get { return this.x; }

    set { this.x = value; }

    }

    public double Y

    {

    get { return this.y; }

    set { this.y = value; }

    }

    public bool IsDragging

    {

    get { return this.isDragging; }

    set { this.isDragging = value; }

    }

    #endregion

    }