SIGN UP MEMBER LOGIN:    
ARTICLE

WPF and user Interactivity Part IV: Attached property for moving and resizing shapes

Posted by Bechir Bejaoui Articles | WPF with C# April 28, 2010
In this article, I will refactor the previous code by representing it as an attached property.
Reader Level:
Download Files:
 

In a previous article, I've shown how to deal with shapes, I mean how to move them and resize them using a couple of mouse event handlers. This article is, in fact, an extension of the previous one; therefore it is highly recommended to take a look on the previous article.

In this article, I will refactor the previous code by representing it as an attached property. The result is as same as the published in the previous article. But this once no business logic is implemented at the code behind level of the window, all business logic is wrapped up within an attached property.

First let's add a class that will hold all the business logic to process shapes either by resizing or moving. Then, let's add an enumeration that will represent either the move or the resize mode. The user could experience exclusively one mode per time interval. The user sets the desired mode through the context menu.

namespace Graphics6.AttachedProperties
{
    public class DragDropResizeClass
    {
    }
    public enum Mode {Move,Resize }
}

The given class holds two properties, one is a Boolean attached property that will expose all the business logic that enables processing shapes within the canvas or the scene once its value is set to true. If it is set to false then shapes remain static, in other word, the user can't move them or resize them. The other property is static too. It is type of Mode (The above defined enumeration), this one will help to tell the attached property which is the actual defined mode by the user.

First let's define the first property

static public Mode ProcessMode { get; set; }

It defines the mode either resize or move one.

Second, this is the attached property

public static bool GetActivated(DependencyObject obj)
 {return (bool)obj.GetValue(ActivatedProperty);}
public static void SetActivated(DependencyObject obj, bool value){obj.SetValue(ActivatedProperty, value);}
// Using a DependencyProperty as the backing store for Activated.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty ActivatedProperty =
            DependencyProperty.RegisterAttached("Activated",
                                               typeof(bool),
                     typeof(DragDropResizeClass), metadata);

As you can Remarque, we used our defined metadata, in fact, this Meta data will hold all the business logic, it is composed by two main parts, namely the default value which is false, the second part is the property changed callback delegate.

static UIPropertyMetadata metadata = new UIPropertyMetadata(false,

             new PropertyChangedCallback(OnPropertyValueChanged));

The property changed callback points to the below method:

static void OnPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
 {
    Canvas canvas = d as Canvas;
    if ((bool)args.NewValue==true)
    {
    canvas.MouseLeftButtonUp +=
    new MouseButtonEventHandler(canvas_MouseLeftButtonUp);
    canvas.MouseLeftButtonDown +=
    new MouseButtonEventHandler(canvas_MouseLeftButtonDown);
    canvas.MouseMove +=
    new MouseEventHandler(canvas_MouseMove); 
    }
     else
     {
                canvas.MouseLeftButtonUp -=
                new MouseButtonEventHandler(canvas_MouseLeftButtonUp);
                canvas.MouseLeftButtonDown -=
                new MouseButtonEventHandler(canvas_MouseLeftButtonDown);
                canvas.MouseMove -=
                new MouseEventHandler(canvas_MouseMove);
      }
  }

The above methods enables to subscribe or unsubscribe the three events, namely the mouse left button down, up and the mouse move to help deal with shapes within the scene, the event handler are exposed as follow:

static UIPropertyMetadata metadata = new UIPropertyMetadata(false,
             new PropertyChangedCallback(OnPropertyValueChanged));

The property changed callback points to the bellow method:

static void OnPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
 {
    Canvas canvas = d as Canvas;
    if ((bool)args.NewValue==true)
    {
    canvas.MouseLeftButtonUp +=
    new MouseButtonEventHandler(canvas_MouseLeftButtonUp);
    canvas.MouseLeftButtonDown +=
    new MouseButtonEventHandler(canvas_MouseLeftButtonDown);
    canvas.MouseMove +=
    new MouseEventHandler(canvas_MouseMove); 

    }
     else
     {
                canvas.MouseLeftButtonUp -=
                new MouseButtonEventHandler(canvas_MouseLeftButtonUp);
                canvas.MouseLeftButtonDown -=
                new MouseButtonEventHandler(canvas_MouseLeftButtonDown);
                canvas.MouseMove -=
                new MouseEventHandler(canvas_MouseMove);
      }
  }

 The above methods enables to subscribe or unsubscribe the three events, namely the mouse left button down, up and the mouse move to help deal with shapes within the scene, the event handler are exposed as follow:

 static void canvas_MouseLeftButtonUp(object sender,
        MouseEventArgs args)
   {
            Canvas Scene = sender as Canvas;
            //First Block
            HitTestResult result = VisualTreeHelper.HitTest(Scene,
                         Mouse.GetPosition(Scene));
            Path path = result.VisualHit as Path;

            //Second block
            if (ProcessMode == Mode.Move)
            {
                if (path.Data.GetType() == typeof(EllipseGeometry))
                {
                    EllipseGeometry geometry =
                    new EllipseGeometry(new Point(50, 50), 50, 50);
                    if ((path.Tag as double[]).Length>0)
                    { 
                        geometry.RadiusX =(path.Tag as double[])[0];;
                        geometry.RadiusY =(path.Tag as double[])[1];
                    }
                    geometry.Transform =
                   new TranslateTransform {
                   X = CurrentPoint.X - geometry.RadiusX,
                   Y = CurrentPoint.Y - geometry.RadiusY };
                    Path FinalPath = new Path {
                    Fill = Brushes.Red, 
                    Stroke = Brushes.Black,
                    Data = geometry };
                    Scene.Children.Add(FinalPath);
                    Scene.Children.Remove(path);
                }
                if (path.Data.GetType() == typeof(RectangleGeometry))

                {
                    RectangleGeometry geometry =
                    new RectangleGeometry(new Rect(0, 0, 150, 100));
                    if ((Rect)path.Tag != null)
                    {
                        geometry.Rect = (Rect)path.Tag;
                    }
                    geometry.Transform =
                    new TranslateTransform {
                    X = CurrentPoint.X - geometry.Rect.Width / 2,
                    Y = CurrentPoint.Y - geometry.Rect.Height / 2 };
                    Path FinalPath = new Path { Fill = Brushes.Blue,
                                             Stroke = Brushes.Black, 
                                             Data = geometry };
                    Scene.Children.Add(FinalPath);
                    Scene.Children.Remove(path);
                }
            }

            //Third block
            if (ProcessMode== Mode.Resize)
            {
                Geometry geometry = path.Data;
                if (path.Data.GetType() == typeof(EllipseGeometry))
                {
                    path.Fill = Brushes.Red;
                    (geometry as EllipseGeometry).RadiusX =
                            (path.Tag as double[])[0];
                    (geometry as EllipseGeometry).RadiusY =
                             (path.Tag as double[])[1];
                }
                if (path.Data.GetType() == typeof(RectangleGeometry))
                {
                    path.Fill = Brushes.Blue;
                   (geometry as RectangleGeometry).Rect =
                     (Rect)path.Tag;
                }
            }

        }
        static void canvas_MouseLeftButtonDown(object sender,
                                            MouseEventArgs args)
        {
            Canvas Scene = sender as Canvas;
            //The first block
            HitTestResult result =
            VisualTreeHelper.HitTest(Scene, Mouse.GetPosition(Scene));
            Path path = result.VisualHit as Path;

            //The second block
            if (path.Data.GetType() == typeof(EllipseGeometry))
            {
                Dimensions[0] = (path.Data as EllipseGeometry).RadiusX;
                Dimensions[1] = (path.Data as EllipseGeometry).RadiusY;
                path.Tag = Dimensions;
            }

            if (path.Data.GetType() == typeof(RectangleGeometry))
            {
                rect = (path.Data as RectangleGeometry).Rect;
                path.Tag = rect;
            }

            //The third block
            path.Fill = Brushes.Violet;

            //The fourth block
            if (StartPoint == null)
            {
                StartPoint = args.GetPosition(Scene);
            }
            StartPoint = CurrentPoint;
        }
        static void canvas_MouseMove(object sender, MouseEventArgs args)
        {
            Canvas Scene = sender as Canvas;
            //First block
            CurrentPoint = args.GetPosition(Scene);

            //Second block
            HitTestResult result =
            VisualTreeHelper.HitTest(Scene, Mouse.GetPosition(Scene));
            Path path = result.VisualHit as Path;

            //Third block
            if (ProcessMode == Mode.Move)
            {
                if (Mouse.LeftButton == MouseButtonState.Pressed)
               
{
                    transform.Matrix =
                    new Matrix(1, 0, 0, 1,
                    CurrentPoint.X - StartPoint.X,
                    
CurrentPoint.Y - StartPoint.Y);
                    path.RenderTransform = transform;
                }
            }
 
            //Fourth block

            {
                Geometry geomerty = path.Data;
                if (Mouse.LeftButton == MouseButtonState.Pressed)
                {
                    if (geomerty.GetType() == typeof(EllipseGeometry))
                    {
                        EllipseGeometry currentShape =
                       geomerty as EllipseGeometry;
                       currentShape.RadiusX =
                       CurrentPoint.X - currentShape.Center.X;
                       currentShape.RadiusY =
                       CurrentPoint.Y - currentShape.Center.Y;
                       double[] Dimensions =
                       new double[2] {
                       currentShape.RadiusX, currentShape.RadiusY };
                       path.Tag = Dimensions;
                    }
                    if (geomerty.GetType() == typeof(RectangleGeometry))
                    {
                        RectangleGeometry currentShape =
                            geomerty as RectangleGeometry;
                        Vector vector =
                            CurrentPoint - currentShape.Rect.Location;
                        Rect rect =
                           new Rect(currentShape.Rect.Location, vector);
                        currentShape.Rect = rect;
                        path.Tag = rect;
                    }
 
                }
            }
        }


The event handlers are explained in advantage in the previous article. This will lead to the exact result of the previous article. In the next article, I will demonstrate how to deal with the drag and drop activities in more realistic scenario.

Good dotnetting!!!

Login to add your contents and source code to this article
share this article :
post comment
 

To duplicate this issue: Add several rectangles (4 or more). Each time move them out of the way. Drag a rectangle over the top of another rectangle. Note that the other rectangle starts to move. More experimentation shows you can have most or all rectangles moving each time. That might be handy as a multi-select feature, but how is it controlled so that the default is only one rectangle moves at a time?

Posted by Kevin Baker Feb 12, 2012
Nevron Gauge for SharePoint
Become a Sponsor
PREMIUM SPONSORS
  • Finally – a virtual platform that delivers next-generation Windows Server 2008 Hyper-V virtualization technology from a managed hosting partner you can truly depend on. Visit www.maximumasp.com/max for a FREE 30 day trial. Hurry offer ends soon. Climb aboard the MaxV platform and take advantage of High Availability, Intelligent Monitoring, Recurrent Backups, and Scalability – with no hassle or hidden fees. As a managed hosting partner focused solely on Microsoft technologies since 2000, MaximumASP is uniquely qualified to provide the superior support that our business is built on. Unparalleled expertise with Microsoft technologies lead to working directly with Microsoft as first to offer IIS 7 and SQL 2008 betas in a hosted environment; partnering in the Go Live Program for Hyper-V; and product co-launches built on WS 2008 with Hyper-V technology.
    ceTE software specializes in components for dynamic PDF generation and manipulation. The DynamicPDF™ product line allows you to dynamically generate PDF documents, merge PDF documents and new content to existing PDF documents from within your applications. Visit DynamicPDF here
Nevron Gauge for SharePoint
Become a Sponsor