Introduction
I needed to draw lines between the nodes and collapsing/expanding of the nodes. Turned out that collapsing/expanding was very easy to implement.
Drawing lines was a bit more difficult.
The solution works perfect but I’m sure there is a simpler approach to this problem. Anyway, if someone needs a solution, this one works.
The treeview:
Using the Code
Below is the ControlTemplate
.
There are 3 rows in the first Grid
.
- The first one is for the horizontal line(s).
- the second one for the
UserControl
.
- The last one holds the
ItemsPresenter
.
<ControlTemplate TargetType="TreeViewItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Line Grid.Column="0" SnapsToDevicePixels="True"
Visibility="{Binding ., UpdateSourceTrigger=PropertyChanged,
Converter={c:IsFlowElementFrom_ToLeftLineVisiblility_Converter}}"
Grid.Row="0" HorizontalAlignment="Stretch"
VerticalAlignment="Bottom" Stroke="Black"
X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
StrokeThickness="2" />
<Line Grid.Column="1" SnapsToDevicePixels="True"
Visibility="{Binding ., UpdateSourceTrigger=PropertyChanged,
Converter={c:IsFlowElementFrom_ToRigthLineVisiblility_Converter}}"
Grid.Row="0" HorizontalAlignment="Stretch"
VerticalAlignment="Bottom" Stroke="Black"
X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
StrokeThickness="2" />
</Grid>
<StackPanel Grid.Row="1" Orientation="Vertical"
HorizontalAlignment="Center">
<f:FlowElementControl FlowElementControl_IsReported_Event=
"FlowElementControl_FlowElementControl_IsReported_Event"
FlowElementControl_IsSelected_Event=
"FlowElementControl_IsSelected_Event"
ButtonExpand_ClickEvent=
"FlowElementControl_ButtonExpand_ClickEvent"
ButtonExtraInfo_ClickEvent=
"FlowElementControl_ButtonExtraInfo_ClickEvent"
FlowElementControl_IsUserResponse_Event=
"FlowElementControl_FlowElementControl_IsUserResponse_Event"
Visibility="{Binding .FlowElementFrom.TreeviewItem_IsExpanded,
UpdateSourceTrigger=PropertyChanged,
Converter={c:IsParentExpandedToVisibility_Converter}}"
Tag="{Binding .}"
HorizontalAlignment="Center">
</f:FlowElementControl>
</StackPanel>
<ItemsPresenter Grid.Row="2">
</ItemsPresenter>
</Grid>
</ControlTemplate>
The horizontal line between the nodes are actually two lines. They are both the half length of the first Grid
. To do this, we place a new Grid
in the first row and give this Grid
2 equally spaced columns (1*) (the width of the first Grid
changes according to the nodes below them, so cannot use a fixed width of the lines).
We need two lines in order to hide or show them accordingly to the position of the nodes.
- If the node is the most left one, we hide the left line. (
TreeviewItem_IsFirst = true
)
- If the node is the most right one, we hide right line. (
TreeviewItem_IsLast = true
)
- If the node is neither the most left or right, we show both lines. (
TreeviewItem_IsFirst = false, TreeviewItem_IsLast = false
)
The first and last nodes are set in the database.
SetFirstAndLast()
We have to do this everytime we add, delete, copy or move items.
You can add a sort order if needed.
private void RecursiveSetFirstLast(Database.FlowElements flowelement)
{
List<Database.FlowElements> toList = flowelement.FlowElementsTo.ToList();
for (int i = 0; i < toList.Count; i++)
{
if (i == 0)
toList[i].TreeviewItem_IsFirst = true;
else
toList[i].TreeviewItem_IsFirst = false;
if (i == toList.Count - 1)
toList[i].TreeviewItem_IsLast = true;
else
toList[i].TreeviewItem_IsLast = false;
if (i != 0 & i != toList.Count - 1)
{
toList[i].TreeviewItem_IsFirst = false;
toList[i].TreeviewItem_IsLast = false;
}
RecursiveSetFirstLast(toList[i]);
}
}
private void SetFirstAndLast()
{
foreach (Database.FlowElements f in flowElementsNotLinked) RecursiveSetFirstLast(f);
}
flowElementsNotLinked = Currenttemplate.FlowElements.Where(f => f.FlowElementFrom == null).ToList();
Next, we need to set the visibility of the lines. This can be done with a converter: (You need two of them, left and right)
class IsFlowElementFrom_ToLeftLineVisiblility_Converter : System.Windows.Markup.MarkupExtension, IValueConverter
{
public IsFlowElementFrom_ToLeftLineVisiblility_Converter()
{
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Database.FlowElements flowelement = (Database.FlowElements)value;
if (flowelement == null || flowelement.FlowElementFrom == null) return Visibility.Hidden;
if (flowelement.TreeviewItem_IsFirst) return Visibility.Hidden;
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return true;
}
private static IsFlowElementFrom_ToLeftLineVisiblility_Converter instance;
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (instance == null)
instance = new IsFlowElementFrom_ToLeftLineVisiblility_Converter();
return instance;
}
}
Last thing to do is draw two short vertical lines in your usercontrol
(top and bottom). And show/hide them based on the elements above or below them. You can use the same principle as above.
Best to replace the bottom line of the control with a menuItem or button with a plus or minus in it. For collapsing and expanding.
<Menu Background="Transparent" HorizontalAlignment="Center" VerticalAlignment="Center">
<MenuItem x:Name="ButtonExpand" Tag="{Binding .}" Visibility="{Binding ., UpdateSourceTrigger=PropertyChanged, Converter={c:FlowelementsTo_ToButtonVisiblility_Converter}}" IsEnabled="True" Click="ButtonExpand_Click">
<MenuItem.Header>
<TextBlock FontSize="12" Text="{Binding ., UpdateSourceTrigger=PropertyChanged, Converter={c:IsExpandedToTextBlockText_Converter}}" xml:space="preserve"></TextBlock>
</MenuItem.Header>
<MenuItem.Effect>
<DropShadowEffect Opacity="0.5" ShadowDepth="4" BlurRadius="8"/>
</MenuItem.Effect>
<MenuItem.ToolTip>
<TextBlock>
Collapse/Expand this flowelement.
</TextBlock>
</MenuItem.ToolTip>
</MenuItem>
</Menu>
Points of Interest
I tried some very different approaches, this was the first one that worked. Next try would have been just drawing on the canvas.
During testing, I gave all containers a different color and margin. This shows you what is actually happing and can be very helpful.
History
- 12th September, 2014: Initial version