|
|||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
IntroductionThere exist different techniques to connect items in a typical diagram designer. One approach is to provide connection elements in a toolbox which the user can drop on the designer canvas and then drag the endpoints to the source and sink items. Another approach is that the items themselves provide connection points from which the user can drag a connection to other items. This second strategy is the one I will explain in this article. Use Case: How to Connect ItemsI'm sure you know how to connect items in a designer application, but still I will illustrate this in some detail to make it easier to identify which class is involved in which activity.
How is a Connection Glued to an Item?The default layout of the connectors is defined in the <ControlTemplate x:Key="ConnectorDecoratorTemplate" TargetType="{x:Type Control}">
<Grid Margin="-5">
<s:Connector Orientation="Left" VerticalAlignment="Center"
HorizontalAlignment="Left"/>
<s:Connector Orientation="Top" VerticalAlignment="Top"
HorizontalAlignment="Center"/>
<s:Connector Orientation="Right" VerticalAlignment="Center"
HorizontalAlignment="Right"/>
<s:Connector Orientation="Bottom" VerticalAlignment="Bottom"
HorizontalAlignment="Center"/>
</Grid>
</ControlTemplate>
A public class Connector : Control, INotifyPropertyChanged
{
private Point position;
public Point Position
{
get { return position; }
set
{
if (position != value)
{
position = value;
OnPropertyChanged("Position");
}
}
}
public Connector()
{
// fired when layout changes
base.LayoutUpdated += new EventHandler(Connector_LayoutUpdated);
}
void Connector_LayoutUpdated(object sender, EventArgs e)
{
DesignerCanvas designer = GetDesignerCanvas(this);
if (designer != null)
{
//get center position of this Connector relative to the DesignerCanvas
this.Position = this.TransformToAncestor(designer).Transform
(new Point(this.Width / 2, this.Height / 2));
}
}
...
}
Now we switch over to the public class Connection : Control, ISelectable, INotifyPropertyChanged
{
private Connector source;
public Connector Source
{
get
{
return source;
}
set
{
if (source != value)
{
if (source != null)
{
source.PropertyChanged -=
new PropertyChangedEventHandler(OnConnectorPositionChanged);
source.Connections.Remove(this);
}
source = value;
if (source != null)
{
source.Connections.Add(this);
source.PropertyChanged +=
new PropertyChangedEventHandler(OnConnectorPositionChanged);
}
UpdatePathGeometry();
}
}
}
void OnConnectorPositionChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals("Position"))
{
UpdatePathGeometry();
}
}
....
}
This snippet shows only the source connector, but the sink connector works analogous. The event handler finally updates the connection path geometry, that's it. Customize Connectors LayoutThe default layout and the number of connectors may not always fit your needs. Take the following example of a triangle shaped <Path IsHitTestVisible="False"
Fill="Orange"
Stretch="Fill"
Data="M 0,10 5,0 10,10 Z">
<s:DesignerItem.DragThumbTemplate>
<ControlTemplate>
<Path Fill="Transparent" Stretch="Fill"
Data="M 0,10 5,0 10,10 Z"/>
</ControlTemplate>
</s:DesignerItem.DragThumbTemplate>
</Path>
The problem here is that the connectors are only visible when the mouse is over the item. If you try to reach the connector on the left or right side you may have some problems. But the solution comes in the form of an attached property named <Path IsHitTestVisible="False"
Fill="Orange"
Stretch="Fill"
Data="M 0,10 5,0 10,10 Z">
<!-- Custom DragThumb Template -->
<s:DesignerItem.DragThumbTemplate>
<ControlTemplate>
<Path Fill="Transparent" Stretch="Fill"
Data="M 0,10 5,0 10,10 Z"/>
</ControlTemplate>
<s:DesignerItem.DragThumbTemplate>
<!-- Custom ConnectorDecorator Template -->
<s:DesignerItem.ConnectorDecoratorTemplate>
<ControlTemplate>
<Grid Margin="0">
<s:Connector Orientation="Top" HorizontalAlignment="Center"
VerticalAlignment="Top" />
<s:Connector Orientation="Bottom" HorizontalAlignment="Center"
VerticalAlignment="Bottom" />
<UniformGrid Columns="2">
<s:Connector Grid.Column="0" Orientation="Left" />
<s:Connector Grid.Column="1" Orientation="Right"/>
</UniformGrid>
</Grid>
</ControlTemplate>
</s:DesignerItem.ConnectorDecoratorTemplate>
</Path>
This solution provides a better result but it still needs some tricky layout, which may not always be feasible. For this I provide a <c:RelativePositionPanel>
<Button Content="TopLeft" c:RelativePositionPanel.RelativePosition="0,0"/>
<Button Content="Center" c:RelativePositionPanel.RelativePosition="0.5,0.5"/>
<Button Content="BottomRight" c:RelativePositionPanel.RelativePosition="1,1"/>
</ControlTemplate>
This panel can be quite handy when it comes to arrange connectors: <Path IsHitTestVisible="False"
Fill="Orange"
Stretch="Fill"
Data="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z">
<!-- Custom DragThumb Template -->
<s:DesignerItem.DragThumbTemplate>
<ControlTemplate>
<Path Fill="Transparent" Stretch="Fill"
Data="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z"/>
</ControlTemplate>
</s:DesignerItem.DragThumbTemplate>
<!-- Custom ConnectorDecorator Template -->
<s:DesignerItem.ConnectorDecoratorTemplate>
<ControlTemplate>
<c:RelativePositionPanel Margin="-4">
<s:Connector Orientation="Top"
c:RelativePositionPanel.RelativePosition="0.5,0"/>
<s:Connector Orientation="Left"
c:RelativePositionPanel.RelativePosition="0,0.385"/>
<s:Connector Orientation="Right"
c:RelativePositionPanel.RelativePosition="1,0.385"/>
<s:Connector Orientation="Bottom"
c:RelativePositionPanel.RelativePosition="0.185,1"/>
<s:Connector Orientation="Bottom"
c:RelativePositionPanel.RelativePosition="0.815,1"/>
</c:RelativePositionPanel>
</ControlTemplate>
</s:DesignerItem.ConnectorDecoratorTemplate>
</Path>
OutlookIn the next article I will concentrate on commands:
History
| ||||||||||||||||||||||||||||||||