[WPF] Comment créer des éléments déplaçables et interconnectés entre eux ?
Pour essayer de comprendre ce que je veux dire, essayer de vous représenter un diagramme de classe: il s'agit de simple rectangle qui sont reliés entre eux 
Imaginez maintenant que vous souhaitiez faire la même chose, mais que vous puissiez également déplacer chacun de ces rectangles: vous seriez en mesure de pouvoir commencer à réaliser des applications dans le même esprit que Visio par exemple 
Attention, il faut bien faire en sorte que les lignes reliant les rectangles se déplacent également sinon, l'affichage ne serait pas logique.
Je vous avais déjà expliqué, dans un précédent post, comment faire en sorte de pouvoir déplacer des éléments au sein d'une application WPF (Windows Presentation Foundation).
J'ai donc repris ce code dans un petit contrôle (héritant de Shape) et je l'ai un peu amélioré:
public class MoveableRectangle : Shape
{
private bool isDragging;
private Point controlPoint, mousePoint, newMousePoint;
private List<ConnectionLine> m_ConnectionLineCollection;
public List<ConnectionLine> ConnectionLineCollection
{
get { return m_ConnectionLineCollection; }
set { m_ConnectionLineCollection = value; }
}
public MoveableRectangle()
{
this.m_ConnectionLineCollection = new List<ConnectionLine>();
}
/// <summary>
/// Propriété utilisée pour indiquer quelle type de forme sera affichée.
/// </summary>
protected override Geometry DefiningGeometry
{
get
{
return new RectangleGeometry(new Rect(new Size(this.Width, this.Height)));
}
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
isDragging = true;
controlPoint = new Point(Canvas.GetLeft(this), Canvas.GetTop(this));
mousePoint = new Point(Mouse.GetPosition((IInputElement)this.Parent).X, Mouse.GetPosition((IInputElement)this.Parent).Y);
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (isDragging == true)
{
newMousePoint = new Point(Mouse.GetPosition((IInputElement)this.Parent).X, Mouse.GetPosition((IInputElement)this.Parent).Y);
Canvas.SetLeft(this, (controlPoint.X + newMousePoint.X - mousePoint.X));
Canvas.SetTop(this, (controlPoint.Y + newMousePoint.Y - mousePoint.Y));
foreach (ConnectionLine line in this.m_ConnectionLineCollection)
{
switch (line.ConnectionLineDirection)
{
case ConnectionLine.LineDirection.Start:
line.ConnectedLine.X1 = Canvas.GetLeft(this);
line.ConnectedLine.Y1 = Canvas.GetTop(this);
break;
case ConnectionLine.LineDirection.End:
line.ConnectedLine.X2 = Canvas.GetLeft(this);
line.ConnectedLine.Y2 = Canvas.GetTop(this);
break;
default:
break;
}
}
}
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
isDragging = false;
}
}
Le code est quasi-identique à la version d'avant, hormis 2 différences:
- l'appel à this.Parent (caster en IInputElement)
- l'ajout d'un foreach permettant de repositionner les lignes reliant les rectangles entre eux.
Je ne vous fais pas l'explication du code, celui-ci devant être assez simple à comprendre
Il ne nous reste plus, à présent, qu'à utiliser notre contrôle au sein de notre application WPF:
<Canvas>
<MyControl:MoveableRectangle x:Name="rect"
Width="70"
Height="40"
Fill="Red"
Canvas.Top="15"
Canvas.Left="45" />
<Line X1="45" Y1="15" X2="95" Y2="145" Stroke="Black" StrokeThickness="1" x:Name="line" />
<MyControl:MoveableRectangle x:Name="rect2"
Width="70"
Height="40"
Fill="Blue"
Canvas.Top="145"
Canvas.Left="95" />
<Line X1="95" Y1="145" X2="150" Y2="200" Stroke="Black" StrokeThickness="1" x:Name="line2" />
<MyControl:MoveableRectangle x:Name="rect3"
Width="70"
Height="40"
Fill="Orange"
Canvas.Top="200"
Canvas.Left="150" />
</Canvas>
Là encore, rien de bien compliqué. Là où cela se corse, c'est qu'il nous faut un moyen pour indiquer à notre contrôle qu'il est relié à un (ou plusieurs) autre éléments. Et c'est à cela que sert la propriété ConnectionLineCollection que j'ai ajouté au contrôle. Pour vous aider à comprendre comment cela fonctionne, j'ai mis des commentaires:
// 1 ligne relie 2 rectangles: on indique le rectangle de départ et le rectangle d'arrivée.
// Rectangle de départ: X1-Y1 de la ligne
// Rectangle d'arrivéet: X2-Y2 de la ligne this.rect.ConnectionLineCollection.Add(new ConnectionLine(this.line, ConnectionLine.LineDirection.Start));
this.rect2.ConnectionLineCollection.Add(new ConnectionLine(this.line, ConnectionLine.LineDirection.End));
this.rect2.ConnectionLineCollection.Add(new ConnectionLine(this.line2, ConnectionLine.LineDirection.Start));
this.rect3.ConnectionLineCollection.Add(new ConnectionLine(this.line2, ConnectionLine.LineDirection.End));
Au final, on obtient bien ce que l'on souhaite, comme vous pouvez le constater sur cette image (si elle ne bouge pas, cliquez sur l'image pour la voir s'animer):
Certes, ce n'est pas optimum (il y aurait 2-3 améliorations à faire pour ce soit mieux) mais cela vous permet de comprendre le principe 
Si vous souhaitez télécharger la source d'exemple, c'est par ici: http://morpheus.developpez.com/wpf/tools/MoveAndLinkElements.zip
En espérant que cela vous aide ;)
A+
PS: Bon Nix, à quand un endroit où je peux poster mes sources WPF sur CodeS-SourceS ? 
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :