Muckers Mucker
Muckers Mucker

Reputation: 109

WPF Treeview with horizontally wrapped leaf items

New to WPF I'm trying to get to grips with a TreeView and I'm incredibly impressed with the flexibility it offers.

So far, each of my treeview items implement an Expander which is great way to display greater detail about each item and have the header show just a summary. However, this is not suitable for the leaf items and is wasteful of screen space - each leaf item (of which there are many) in my treeview shows a comparatively small amount of data.

What I want to implement for the leaf items is horizontal wrapping as opposed to a vertical listing. Imagine a grid or (stackpanel) displayed and each item is displayed in its own cell/region wrapping onto the row below as available horizontal space dictates.

for example

Level 1
  Level 2
     Level 3
      Leaf 1 | leaf 2 | leaf 3 | leaf 4
      Leaf 5 | leaf 6 | leaf 7 | leaf 8
      Leaf 9 .....

Level 1
  Level 2
     Level 3
       Leaf 1 | leaf 2|  ....

I've search for ages for this - I've read about TreeGrid (which is what I've already independently implemented) and also the excellent http://www.codeproject.com/Articles/17025/Custom-TreeView-Layout-in-WPF example. This goes someway to fulfill what I want but doesn't implement the wrapping of items.

My background is heavily steeped in WinForms and I'm restricted in my lack of WPF experience (I love what I've learnt so far). Is what I want to do, doable?

I'm not asking for a boilerplate, cut-and-paste solution, just some pointers/resources/opinions that I can explore.

BTW, because of the skinflint nature of my company, any solution must be free.

Thank you.

Here's the xaml for the TreeView so far:

 <TreeView Name="tvMonitoredAlarms"  Margin="10,10,10,10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Auto" >
                <TreeView.ItemContainerStyle>
                    <Style TargetType="TreeViewItem">
                        <Setter Property="IsExpanded" Value="{Binding Expanded}"/>
                    </Style>
                </TreeView.ItemContainerStyle>


                <TreeView.Resources>

                    <!-- *****************************************************************************************************************
                     Server TreeView item 
                     ***************************************************************************************************************** -->
                    <HierarchicalDataTemplate DataType="{x:Type PAM:MonitoredServer}" ItemsSource="{Binding PLCs}">

                        <Border Margin="0" BorderBrush="{Binding AlarmPriority.BackColour, Converter={StaticResource PriorityBrush}}" 
                          Background="{Binding AlarmPriority.BackColour, Converter={StaticResource PriorityBrush}}">
                            <Border.Style>
                                <Style TargetType="{x:Type Border}">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Path=OPCAlarmTriggered}" Value="True">
                                            <Setter Property="BorderThickness" Value="5"/>
                                            <Setter Property="CornerRadius" Value="3,3,3,3"/>
                                            <DataTrigger.EnterActions>
                                                <BeginStoryboard x:Name="FlashBorderSERVER" Storyboard="{StaticResource FlashBorder}"/>
                                            </DataTrigger.EnterActions>
                                            <DataTrigger.ExitActions>
                                                <StopStoryboard BeginStoryboardName="FlashBorderSERVER"/>
                                            </DataTrigger.ExitActions>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding Path=OPCAlarmTriggered}" Value="False">

                                            <Setter Property="BorderThickness" Value="1"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Border.Style>

                            <Expander Template="{StaticResource RevealExpanderTemp}"
                                OverridesDefaultStyle="True"
                                Header="{Binding ServerName}"
                                HorizontalAlignment="Left"
                                VerticalAlignment="Top"

                                >

                                <Grid Margin="0,0,0,0" HorizontalAlignment="Stretch">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="50*"/>
                                        <ColumnDefinition/>

                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition/>
                                        <RowDefinition/>
                                        <RowDefinition/>
                                        <RowDefinition/>
                                    </Grid.RowDefinitions>
                                    <Label Margin="4,0,4,0" Grid.Row="0" Grid.Column="0">PLCs</Label>
                                    <Label Margin="4,0,4,0" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding NumberOfPLCs}" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" />
                                        <Label Margin="4,0,4,0" Grid.Row="1" Grid.Column="0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}">Monitored Alarms</Label>
                                        <Label Margin="4,0,4,0" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding NumberOfMonitoredAlarms}" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" />
                                        <Label Margin="4,0,4,0" Grid.Row="2" Grid.Column="0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}">Address</Label>
                                    <Label Margin="4,0,4,0" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding IPAddress}" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}"/>
                                    <Label Margin="4,0,4,0" Grid.Row="3" Grid.Column="0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}">Comment</Label>
                                    <Label Margin="4,0,4,0" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding Comment}" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}"/>

                                </Grid>

                            </Expander>
                        </Border>
                    </HierarchicalDataTemplate>
                    <!-- *****************************************************************************************************************
                     PLC TreeView item 
                     ***************************************************************************************************************** -->
                    <HierarchicalDataTemplate DataType="{x:Type PAM:MonitoredPLC}" ItemsSource="{Binding Areas}">
                        <Border Margin="0" BorderBrush="{Binding AlarmPriority.BackColour, Converter={StaticResource PriorityBrush}}" 
                        Background="{Binding AlarmPriority.BackColour, Converter={StaticResource PriorityBrush}}"
                                >
                            <Border.Style>
                                <Style TargetType="{x:Type Border}">

                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Path=OPCAlarmTriggered}" Value="True">
                                            <Setter Property="BorderThickness" Value="5"/>
                                            <Setter Property="CornerRadius" Value="3,3,3,3"/>
                                            <DataTrigger.EnterActions>
                                                <BeginStoryboard x:Name="FlashBorderPLC" Storyboard="{StaticResource FlashBorder}"/>
                                            </DataTrigger.EnterActions>
                                            <DataTrigger.ExitActions>
                                                <StopStoryboard BeginStoryboardName="FlashBorderPLC"/>
                                            </DataTrigger.ExitActions>

                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding Path=OPCAlarmTriggered}" Value="False">

                                            <Setter Property="BorderThickness" Value="1"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Border.Style>
                            <Expander Template="{StaticResource RevealExpanderTemp}"
                                OverridesDefaultStyle="True"
                                Header="{Binding Name}"
                                HorizontalAlignment="Left"
                                VerticalAlignment="Top"
                                >

                                <Expander.Background>
                                    <LinearGradientBrush ColorInterpolationMode="ScRgbLinearInterpolation" StartPoint="0,0.5" EndPoint="1,0.5">

                                        <GradientStop Color="{Binding AlarmPriority.BackColour, Converter={StaticResource PriorityBrush}}" Offset="0"/>

                                        <GradientStop Color="DarkKhaki" Offset="0.75"/>

                                    </LinearGradientBrush>
                                </Expander.Background>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="200"/>
                                        <ColumnDefinition Width="*"/>
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition/>
                                        <RowDefinition/>
                                        <RowDefinition/>
                                        <RowDefinition/>
                                    </Grid.RowDefinitions>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="0" Grid.Column="0">Areas</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding NumberOfAreas}" />
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="1" Grid.Column="0">Monitored Alarms</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding NumberOfMonitoredAlarms}" />
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="2" Grid.Column="0">Device</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding Device}" />
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="3" Grid.Column="0">Model</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding Model}" />
                                </Grid>
                            </Expander>
                        </Border>

                    </HierarchicalDataTemplate>
                    <!-- *****************************************************************************************************************
                     Area TreeView item 
                     ***************************************************************************************************************** -->
                    <HierarchicalDataTemplate DataType="{x:Type PAM:MonitoredArea}" ItemsSource="{Binding Alarms}">
                        <Border Margin="0" BorderBrush="{Binding AlarmPriority.BackColour, Converter={StaticResource PriorityBrush}}"
                        Background="{Binding AlarmPriority.BackColour, Converter={StaticResource PriorityBrush}}">

                            <Border.Style>
                                <Style TargetType="{x:Type Border}">

                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Path=OPCAlarmTriggered}" Value="True">
                                            <Setter Property="BorderThickness" Value="5"/>
                                            <Setter Property="CornerRadius" Value="3,3,3,3"/>
                                            <DataTrigger.EnterActions>
                                                <BeginStoryboard x:Name="FlashBorderAREA" Storyboard="{StaticResource FlashBorder}"/>
                                            </DataTrigger.EnterActions>
                                            <DataTrigger.ExitActions>
                                                <StopStoryboard BeginStoryboardName="FlashBorderAREA"/>
                                            </DataTrigger.ExitActions>

                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding Path=OPCAlarmTriggered}" Value="False">

                                            <Setter Property="BorderThickness" Value="1"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Border.Style>
                            <Expander Template="{StaticResource RevealExpanderTemp}"
                                OverridesDefaultStyle="True"
                                Header="{Binding OPCArea.DBArea.fldDescription}"
                                HorizontalAlignment="Left"
                                VerticalAlignment="Top"
                                >
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition/>
                                        <ColumnDefinition Width="100"/>
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition/>
                                    </Grid.RowDefinitions>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="0" Grid.Column="0">Monitored Alarms</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding NumberOfMonitoredAlarms}" />

                                </Grid>
                            </Expander>
                        </Border>
                    </HierarchicalDataTemplate>


                    <!-- *****************************************************************************************************************
                     Alarm TreeView item 
                     ***************************************************************************************************************** -->
                    <DataTemplate DataType="{x:Type PAM:MonitoredAlarm}">
                        <Border Name="AlarmBorder"
                                BorderBrush="{Binding AlarmPriority.BackColour, Converter={StaticResource PriorityBrush}}" 
                                Background="{Binding AlarmPriority.BackColour, Converter={StaticResource PriorityBrush}}" 
                                >
                            <Border.Style>
                                <Style TargetType="{x:Type Border}">

                                     <Style.Triggers>
                                        <DataTrigger Binding="{Binding Path=OPCAlarmTriggered}" Value="True">
                                            <Setter Property="BorderThickness" Value="5"/>
                                            <Setter Property="CornerRadius" Value="3,3,3,3"/>

                                            <DataTrigger.EnterActions>
                                                <BeginStoryboard x:Name="FlashBorder" Storyboard="{StaticResource FlashBorder}"/>
                                            </DataTrigger.EnterActions>
                                            <DataTrigger.ExitActions>
                                                <StopStoryboard BeginStoryboardName="FlashBorder"/>
                                            </DataTrigger.ExitActions>

                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding Path=OPCAlarmTriggered}" Value="False">

                                            <Setter Property="BorderThickness" Value="1"/>
                                        </DataTrigger>
                                    </Style.Triggers> 
                                </Style>
                            </Border.Style>

                            <Expander Template="{StaticResource RevealExpanderTemp}"
                            OverridesDefaultStyle="True"
                            Header="{Binding Description}"
                            HorizontalAlignment="Left"
                            VerticalAlignment="Top"
                            Width="400"
                            >

                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="200"/>
                                        <ColumnDefinition/>
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition/>
                                        <RowDefinition/>
                                        <RowDefinition/>
                                        <RowDefinition/>
                                        <RowDefinition/>
                                    </Grid.RowDefinitions>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="0" Grid.Column="0">Current Value</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding OPCAlarm.OPCValue}" />


                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="1" Grid.Column="0">Priority</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding AlarmPriority.DBPriority.fldName}" />
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding TagName}" />
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="4" Grid.Column="0">Address</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding Address}" />
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="5" Grid.Column="0">Scan rate</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding ScanRate}" />
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding LastActive}" />
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="3" Grid.Column="0">Activity</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding Activity}" />
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="4" Grid.Column="0">Comment</Label>
                                    <Label Margin="4,0,4,0" Foreground="{Binding AlarmPriority.ForeColour, Converter={StaticResource PriorityBrush}}" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Right" Content="{Binding DBMonitoredAlarm.fldComment}" />
                                </Grid>
                            </Expander>
                    </Border>
                </DataTemplate>
            </TreeView.Resources>
        </TreeView>

Upvotes: 1

Views: 631

Answers (1)

Muckers Mucker
Muckers Mucker

Reputation: 109

OK, I solved this by restructuring the data displayed in the treenodes into a collection of "rows" where each row can accommodate upto 10 values. This collection is then bound to a datagrid (which can have up to 10 columns) which is displayed in the data template within the treeview.

Not entirely perfect, but it suits my needs.

Upvotes: 1

Related Questions