65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 中自定义 TreeView 布局

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2014 年 9 月 12 日

CPOL

2分钟阅读

viewsIcon

28499

这是“WPF 中自定义 TreeView 布局”的替代方案。

引言

我需要绘制节点之间的线条,以及折叠/展开节点的功能。 结果发现折叠/展开的实现非常简单。

绘制线条稍微困难一些。

这个解决方案运行良好,但我相信一定有更简单的实现方法。 无论如何,如果有人需要解决方案,这个方案是可行的。

TreeView

Using the Code

以下是 ControlTemplate

第一个 Grid 中有 3 行。

  • 第一行用于水平线。
  • 第二行用于 UserControl
  • 最后一行包含 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>

节点之间的水平线实际上是两条线。 它们都是第一个 Grid 的一半长度。 为了做到这一点,我们在第一行中放置一个新的 Grid,并给这个 Grid 两个等间距的列 (1*) (第一个 Grid 的宽度会根据下面的节点变化,因此不能使用线条的固定宽度)。

我们需要两条线,以便根据节点的位置隐藏或显示它们。

  • 如果节点是最左边的,则隐藏左侧线条。(TreeviewItem_IsFirst = true
  • 如果节点是最右边的,则隐藏右侧线条。(TreeviewItem_IsLast = true
  • 如果节点既不是最左边的也不是最右边的,则显示两条线。(TreeviewItem_IsFirst = false, TreeviewItem_IsLast = false

第一个和最后一个节点是在数据库中设置的。

SetFirstAndLast()

每次添加、删除、复制或移动项目时,都需要这样做。

如果需要,可以添加排序顺序。

        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();

接下来,我们需要设置线条的可见性。 这可以使用转换器完成:(你需要两个,分别用于左侧和右侧)

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

最后要做的是在你的 usercontrol 中绘制两条短的垂直线(顶部和底部)。 并根据其上方的元素或下方的元素显示/隐藏它们。 你可以使用上述相同的原理。

最好用带有加号或减号的菜单项或按钮替换控件的底部线条,用于折叠和展开。

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

关注点

我尝试了许多不同的方法,这是第一个有效的方法。 下一个尝试可能是直接在画布上绘制。

在测试期间,我给所有容器设置了不同的颜色和边距。 这向你展示了实际发生的情况,并且可能非常有帮助。

历史

  • 2014 年 9 月 12 日:初始版本
© . All rights reserved.