1
votes

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>
1
Interesting challenge. I think you'll have to retemplate TreeViewItem for this one. Do you have any existing XAML we can work on? if so, please post it here - Federico Berasategui
Re-templating - I had come to the same conclusion, that's what the codeproject implemented.I haven't any xaml to hand immediately (wait until work on Monday!), but it consists of Hierarchical datatemplates (one of each level, expander containing a grid) followed by a final datatemplate. This simply includes an Expander per leaf node. P'haps it'll help to explain what I'm writing - an OPC Control/System. The interface will(does) display list of OPC Servers/Devices and each leaf represents an alarm - there could be dozens. So to .. (continued due to lack of chars..) - Muckers Mucker
maximize screen real estate each of these leaves would be displayed in the manner described ; the user could click on one and then learn more infomation about it. The idea is that this interface imparts max information for least user interface. But, for the life of me I can't find an example of how this has been implemented prior. I don't expect someone to solve this for me, just to give me a helpful nudge in the right direction. - Muckers Mucker

1 Answers

1
votes

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.