WPF Treeview с горизонтально обернутыми листовыми элементами

Новичок в WPF Я пытаюсь разобраться с TreeView, и я невероятно впечатлен гибкостью, которую он предлагает.

Пока что каждый из моих элементов древовидной структуры реализует Expander, который является отличным способом отображения более подробной информации о каждом элементе, а в заголовке отображается только сводка. Однако это не подходит для конечных элементов и занимает расточительное пространство на экране - каждый конечный элемент (а их много) в моем древовидном представлении показывает сравнительно небольшой объем данных.

То, что я хочу реализовать для конечных элементов, - это горизонтальная упаковка, а не вертикальный листинг. Представьте, что отображается сетка или (панель стека), и каждый элемент отображается в своей собственной ячейке / области, оборачиваясь на строку ниже, как того требует доступное горизонтальное пространство.

Например

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

Я много лет искал для этого - я читал о TreeGrid (это то, что я уже независимо реализовал), а также о превосходном http://www.codeproject.com/Articles/17025/Custom-TreeView-Layout-in-WPF. Это каким-то образом выполняет то, что я хочу, но не реализует упаковку элементов.

У меня большой опыт работы с WinForms, и я ограничен в моем отсутствии опыта работы с WPF (мне нравится то, что я узнал до сих пор). То, что я хочу сделать, выполнимо?

Я не прошу шаблонного решения «вырезать и вставить», просто некоторые указатели / ресурсы / мнения, которые я могу изучить.

Кстати, из-за скудного характера моей компании любое решение должно быть бесплатным.

Спасибо.

Вот пока что xaml для TreeView:

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

person Muckers Mucker    schedule 11.01.2014    source источник
comment
Интересный вызов. Думаю, тебе придется переделать TreeViewItem для этого. Есть ли у вас существующий XAML, над которым мы можем работать? если да, пожалуйста, опубликуйте это здесь   -  person Federico Berasategui    schedule 11.01.2014
comment
Повторное создание шаблонов - я пришел к такому же выводу, что и реализовал проект codeproject. У меня сейчас нет никакого xaml под рукой (дождитесь работы в понедельник!), Но он состоит из иерархических шаблонов данных (по одному каждого уровня, расширитель, содержащий сетка), за которой следует окончательная табличка с данными. Сюда просто входит расширитель на каждый листовой узел. Возможно, это поможет объяснить, что я пишу - OPC Control / System. Интерфейс будет отображать (отображает) список серверов / устройств OPC, и каждый лист представляет собой аварийный сигнал - их может быть несколько десятков. Итак, чтобы .. (продолжение из-за отсутствия символов ..)   -  person Muckers Mucker    schedule 11.01.2014
comment
максимально увеличить площадь экрана, каждый из этих листков будет отображаться описанным образом; пользователь мог щелкнуть по одному из них, а затем узнать о нем больше информации. Идея состоит в том, что этот интерфейс передает максимум информации для минимального количества пользовательского интерфейса. Но, хоть убей, я не могу найти пример того, как это было реализовано ранее. Я не жду, что кто-то решит эту проблему за меня, просто чтобы подтолкнуть меня в правильном направлении.   -  person Muckers Mucker    schedule 11.01.2014


Ответы (1)


Хорошо, я решил это, реструктурируя данные, отображаемые в трех узлах, в коллекцию «строк», где каждая строка может вместить до 10 значений. Затем эта коллекция привязывается к сетке данных (которая может иметь до 10 столбцов), которая отображается в шаблоне данных в древовидной структуре.

Не совсем идеально, но мне подходит.

person Muckers Mucker    schedule 30.01.2014