创建可拖拉的图形及连接图形的线条

使用wpf轻松创建可拖拉的图形,形状或控件,并且在这些对象之间用线条相连,在拖动对象时线条也跟随移动。

就像visio, powerpoint中一样的

 

转自 from:   http://dvuyka.spaces.live.com/Blog/cns!305B02907E9BE19A!155.entry

 

 

WPF. Draggable objects and simple shape connectors

Last weekend I found very good sample of using Thumb class for implementing draggable objects. In two words as all other elements in WPF the Thumb also can be templated. And nothing stops you to get all drag functionality of Thumb turning it to anything you need. That's perfect I think ;)

So how to easily create a simple draggable object based on a Thumb?

<
Thumb
 Name
="myThumb"
 DragDelta
="onDragDelta"
 Canvas.Left
="0"
 Canvas.Top
="0"
 Template
="{
StaticResource
 template1
}"/>

Here we declare a Thumb, set the "onDragDelta" handler for the "DragDelta" event and assign to it custom template called "template1".

After that we create the most simpliest shape template that can be found everywhere in the internet

<
ControlTemplate
 x
:
Key
="template1">
< Ellipse Width ="60" Height ="30" Fill ="Black"/>
</ ControlTemplate >

As you can see it turns our thumb to a dummy black shape of (60;30) size.

 

The complete xaml for the window will be as following

<
Window
 x
:
Class
="ShapeConnectors.Window1"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns : x ="http://schemas.microsoft.com/winfx/2006/xaml"
Title ="DragableObject" Height ="300" Width ="300"
>
< Window.Resources >
< ResourceDictionary >
< ControlTemplate x : Key ="template1">
< Ellipse Width ="60" Height ="30" Fill ="Black"/>
</ ControlTemplate >
</ ResourceDictionary >
</ Window.Resources >

< Canvas Name ="myCanvas">
< Thumb Name ="myThumb" DragDelta ="onDragDelta" Canvas.Left ="0" Canvas.Top ="0" Template ="{ StaticResource template1 }"/>
</ Canvas >
</
Window >

draggableobject

As our dummy black shape is still a Thumb element it needs actually one event hanler for the basic drag support implementation - "onDragDelta". Implementing code behind is this way is too boring...

void
 onDragDelta(object
 sender, DragDeltaEventArgs
 e)
{
Canvas .SetLeft(myThumb, Canvas .GetLeft(myThumb) + e.HorizontalChange);
Canvas .SetTop(myThumb, Canvas .GetTop(myThumb) + e.VerticalChange);
}

We get the original position of the element being dragged and add the new offset values.

 

After playing a couple of minutes with the sample above I decided to complicate it a bit to get something more intresting. I wanted to create some workflow-like draggable objects connected to each other with simple shape connectors using basic line geometry. Upon moving the shapes across the canvas line connectors should followed the objects too. Additionally I wanted to have possibility of adding new shapes by clicking at any place of the canvas with establishing any simple line connector to the existing object.

Something like this

shapeconnectors_1 shapeconnectors_2 shapeconnectors_3

 

As far as we get the task to play, what will be the most simple concept of getting the desired result?

Each shape can possibly be connected to any number of another shapes. For hanling the position of each connector while dragging the object we need to somehow control the start and end points of the line element connected to two shapes. So it becomes obvious that each shape should contain the list of line's start and end points separately so that line positioning and length can be easily updated by the shape itself or outter code.

Let's inherit the basic Thumb class providing the required functionality

MyThumb.cs

using
 System.Collections.Generic;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace ShapeConnectors
{
public class MyThumb : Thumb
{
public List <LineGeometry > EndLines { get ; private set ; }
public List <LineGeometry > StartLines { get ; private set ; }

public MyThumb() : base ()
{
StartLines = new List <LineGeometry >();
EndLines = new List <LineGeometry >();
}
}
}

It's very easy now to change the xaml part to use our extended Thumb element

<
Window
 x
:
Class
="ShapeConnectors.Window1"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns : x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns : my ="clr-namespace:ShapeConnectors"
Title ="Window1" Height ="376.25" Width ="801.25" Loaded ="Window_Loaded">
< Canvas Name ="myCanvas">
< my : MyThumb x : Name ="myThumb1" DragDelta ="onDragDelta" Canvas.Left ="270" Canvas.Top ="63.75" Template ="{ StaticResource template1 }"/>
</ Canvas >
</
Window >
 
Here we define our hamespace "ShapeConnectors" and prefix "my". 

Note that should name the element by using "x:Name" syntax because we put the existing though inherited element to xaml.


As can be seen from the screenshots, our shape should contain an icon and a name elements. Let's change thumb's template to get it working.

<
ControlTemplate
 x
:
Key
="template1">
< StackPanel >
< Image Name ="tplImage" Source ="Images/user1.png" Stretch ="Uniform" Width ="32" Height ="32"/>
< TextBlock Name ="tplTextBlock" Text ="User stage"/>
</ StackPanel >
</ ControlTemplate >

We provide a default template for all the draggable objects. Each object contains an image element referencing "Images/user1.png" picture from the resources and contains a text block "User stage" (you can change it to anything you want). Later we will access this template directly from the code, so it is important to name the elements.

Full xaml for our window will be the following

<
Window
 x
:
Class
="ShapeConnectors.Window1"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns : x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns : my ="clr-namespace:ShapeConnectors"
Title ="Window1" Height ="376" Width ="801" Loaded ="Window_Loaded">
< Window.Resources >
< ResourceDictionary >
< ControlTemplate x : Key ="template1">
< StackPanel >
< Image Name ="tplImage" Source ="Images/user1.png" Stretch ="Uniform" Width ="32" Height ="32"/>
< TextBlock Name ="tplTextBlock" Text ="User stage"/>
</ StackPanel >
</ ControlTemplate >
</ ResourceDictionary >
</ Window.Resources >
< Canvas Name ="myCanvas">
< my : MyThumb x : Name ="myThumb1" DragDelta ="onDragDelta" Canvas.Left ="270" Canvas.Top ="63.75" Template ="{ StaticResource template1 }"/>
< my : MyThumb x : Name ="myThumb2" DragDelta ="onDragDelta" Canvas.Left ="270" Canvas.Top ="212.5" Template ="{ StaticResource template1 }"/>
< my : MyThumb x : Name ="myThumb3" DragDelta ="onDragDelta" Canvas.Left ="430" Canvas.Top ="212.5" Template ="{ StaticResource template1 }"/>
< my : MyThumb x : Name ="myThumb4" DragDelta ="onDragDelta" Canvas.Left ="430" Canvas.Top ="63.75" Template ="{ StaticResource template1 }"/>
< Button Canvas.Left ="15" Canvas.Top ="16" Height ="22" Name ="btnNewAction" Width ="75" Click ="btnNewAction_Click"> new action </ Button >
</ Canvas >
</
Window >

I've added four thumbs by default. Additionally I've created a button called "btnNewAction" that will be enabling the mode of adding new objects by clicking somewhere on the canvas. One button click - one thumb to be created anywhere on the canvas and linked to the predefined "myThumb2" element.

As for line geometry we'll be using the Path element. Each path element will be hosting one line.

So here's going the main part of our application

Window1.xaml.cs

using
 System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace ShapeConnectors
{
public partial class Window1 : Window
{
// simple flag for enabling "New thumb" mode
bool isAddNew = false ;

// Paths for our predefined thumbs
Path path1;
Path path2;
Path path3;
Path path4;

public Window1()
{
InitializeComponent();
}

// Event hanlder for dragging functionality support same to all thumbs
private void onDragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
MyThumb thumb = e.Source as MyThumb ;

double left = Canvas .GetLeft(thumb) + e.HorizontalChange;
double top = Canvas .GetTop(thumb) + e.VerticalChange;

Canvas .SetLeft(thumb, left);
Canvas .SetTop(thumb, top);

// Update lines's layouts
UpdateLines(thumb);
}

// This method updates all the starting and ending lines assigned for the given thumb
// according to the latest known thumb position on the canvas
private void UpdateLines(MyThumb thumb)
{
double left = Canvas .GetLeft(thumb);
double top = Canvas .GetTop(thumb);

for (int i = 0; i < thumb.StartLines.Count; i++)
thumb.StartLines[i].StartPoint = new Point (left + thumb.ActualWidth / 2, top + thumb.ActualHeight / 2);

for (int i = 0; i < thumb.EndLines.Count; i++)
thumb.EndLines[i].EndPoint = new Point (left + thumb.ActualWidth / 2, top + thumb.ActualHeight / 2);
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Move all the predefined thumbs to the front to be over the lines
Canvas .SetZIndex(myThumb1, 1);
Canvas .SetZIndex(myThumb2, 1);
Canvas .SetZIndex(myThumb3, 1);
Canvas .SetZIndex(myThumb4, 1);

#region Initialize paths for predefined thumbs
path1 = new Path ();
path1.Stroke = Brushes .Black;
path1.StrokeThickness = 1;

path2 = new Path ();
path2.Stroke = Brushes .Blue;
path2.StrokeThickness = 1;

path3 = new Path ();
path3.Stroke = Brushes .Green;
path3.StrokeThickness = 1;

path4 = new Path ();
path4.Stroke = Brushes .Red;
path4.StrokeThickness = 1;

myCanvas.Children.Add(path1);
myCanvas.Children.Add(path2);
myCanvas.Children.Add(path3);
myCanvas.Children.Add(path4);
#endregion

#region
Initialize line geometry for predefined thumbs
LineGeometry line1 = new LineGeometry ();
path1.Data = line1;

LineGeometry line2 = new LineGeometry ();
path2.Data = line2;

LineGeometry line3 = new LineGeometry ();
path3.Data = line3;

LineGeometry line4 = new LineGeometry ();
path4.Data = line4;
#endregion

#region
Setup connections for predefined thumbs
myThumb1.StartLines.Add(line1);
myThumb2.EndLines.Add(line1);

myThumb2.StartLines.Add(line2);
myThumb3.EndLines.Add(line2);

myThumb3.StartLines.Add(line3);
myThumb4.EndLines.Add(line3);

myThumb4.StartLines.Add(line4);
myThumb1.EndLines.Add(line4);
#endregion

#region
Update lines' layouts
UpdateLines(myThumb1);
UpdateLines(myThumb2);
UpdateLines(myThumb3);
UpdateLines(myThumb4);
#endregion

this .PreviewMouseLeftButtonDown += new MouseButtonEventHandler (Window1_PreviewMouseLeftButtonDown);
}

// Event handler for creating new thumb element by left mouse click
// and visually connecting it to the myThumb2 element
void Window1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (isAddNew)
{
// Create new thumb object
MyThumb newThumb = new MyThumb ();
// Assign our custom template to it
newThumb.Template = this .Resources["template1" ] as ControlTemplate ;
// Calling ApplyTemplate enables us to navigate the visual tree right now (important!)
newThumb.ApplyTemplate();
// Add the "onDragDelta" event handler that is common to all objects
newThumb.DragDelta +=new DragDeltaEventHandler (onDragDelta);
// Put newly created thumb on the canvas
myCanvas.Children.Add(newThumb);

// Access the image element of our custom template and assign it to the new image
Image img = (Image )newThumb.Template.FindName("tplImage" , newThumb);
img.Source = new BitmapImage (new Uri ("Images/gear_connection.png" , UriKind .Relative));

// Access the textblock element of template and change it too
TextBlock txt = (TextBlock )newThumb.Template.FindName("tplTextBlock" , newThumb);
txt.Text = "System action" ;

// Set the position of the object according to the mouse pointer
Point position = e.GetPosition(this );
Canvas .SetLeft(newThumb, position.X);
Canvas .SetTop(newThumb, position.Y);
// Move our thumb to the front to be over the lines
Canvas .SetZIndex(newThumb, 1);
// Manually update the layout of the thumb (important!)
newThumb.UpdateLayout();

// Create new path and put it on the canvas
Path newPath = new Path ();
newPath.Stroke = Brushes .Black;
newPath.StrokeThickness = 1;
myCanvas.Children.Add(newPath);

// Create new line geometry element and assign the path to it
LineGeometry newLine = new LineGeometry ();
newPath.Data = newLine;

// Predefined "myThumb2" element will host the starting point
myThumb2.StartLines.Add(newLine);
// Our new thumb will host the ending point of the line
newThumb.EndLines.Add(newLine);

// Update the layout of line geometry
UpdateLines(newThumb);
UpdateLines(myThumb2);

isAddNew = false ;
Mouse .OverrideCursor = null ;
btnNewAction.IsEnabled = true ;
e.Handled = true ;
}
}

// Event handler for enabling new thumb creation by left mouse button click
private void btnNewAction_Click(object sender, RoutedEventArgs e)
{
isAddNew = true ;
Mouse .OverrideCursor = Cursors .SizeAll;
btnNewAction.IsEnabled = false ;
}
}
}

 

Here's what we can have upon playing a bit with the applicaition

shapeconnectors_4

This sample if too far from the real life application but I tried to keep the code as simple as possible for all to be able to investigate the process and find out own ways of implementing the desired idea.

Have a nice testing and coding.

Source code for the article

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值