Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF
Alternative
Article

Custom TreeView Layout in WPF

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
12 Sep 2014CPOL2 min read 26.2K   19   7
This is an alternative for "Custom TreeView Layout in WPF"

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:

Image 1

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.
XML
<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.

Image 2

C#
SetFirstAndLast()

We have to do this everytime we add, delete, copy or move items.

You can add a sort order if needed.

C#
        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()
        {
            //set first and last, so we can draw the treelines properly
            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)

C#
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;
            //do not show the line if there are no elements above
            if (flowelement == null || flowelement.FlowElementFrom == null) return Visibility.Hidden;
            //don't show the left line if it is the first element
            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.

XML
<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>

Image 3

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionEmergency Pin
Harun Taşdemir18-Jul-20 1:04
Harun Taşdemir18-Jul-20 1:04 
QuestionFull source Pin
User 1088693326-Mar-15 23:51
User 1088693326-Mar-15 23:51 
AnswerRe: Full source Pin
Jan Bakker27-Mar-15 0:14
Jan Bakker27-Mar-15 0:14 
GeneralRe: Full source Pin
Jan Bakker27-Mar-15 0:25
Jan Bakker27-Mar-15 0:25 
QuestionNot an article Pin
Akhil Mittal15-Sep-14 19:27
professionalAkhil Mittal15-Sep-14 19:27 
Consider posting this as a tip or else, explain the post thoroughly.
Thanks
Do not forget to comment and rate the article if it helped you by any means.

AnswerRe: Not an article Pin
Jan Bakker16-Sep-14 0:48
Jan Bakker16-Sep-14 0:48 
QuestionHave you consider Pin
Nelek15-Sep-14 12:16
protectorNelek15-Sep-14 12:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.