嵌套有限深度的视觉元素

本文关键字:视觉 元素 深度 嵌套 | 更新日期: 2023-09-27 18:30:09

我有一个视图模型列表,我想在ItemControl 中显示它

ItemsControl的ItemPanel设置为<Canvas>,元素是不同类型的ViewModel,它们都继承自具有LeftTopWidthHeightElements属性的一个ViewModel(ElementViewModel)。元素的位置由样式设置:

<ItemsControl ItemsSource="{Binding Elements}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.Left" Value="{Binding Left}"/>
            <Setter Property="Canvas.Top" Value="{Binding Top}"/>
            <Setter Property="Width" Value="{Binding Width}"/>
            <Setter Property="Height" Value="{Binding Height}"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

项目属于不同类型,视图由DataTemplates 检索

<DataTemplate DataType="{x:Type local:FirstTypeElementViewModel}">
    <local:firstElementType />
</DataTemplate>
<DataTemplate DataType="{x:Type local:SecondTypeElementViewModel}">
    <local:secondElementType />
</DataTemplate>

现在的问题是,我想在有限的深度内显示这些元素的子元素。我试图将ItemsControl移到一个自定义控件中,然后将其嵌入到我的每个元素视图中,但这种情况下的可重复性并不受任何限制,这会导致显示带有所有子元素的所有元素和较差的性能。

我该如何实现我的目标?

嵌套有限深度的视觉元素

我们有一个树数据结构,希望显示它,但不超过特定级别。正确的

首先,我们需要以某种方式获得元素的深度——它与根部的距离。让我们的元素具有额外的属性int Depth

public class ElementViewModel : INotifyPropertyChanged
{
    public int Depth {get;set;}
    public IEnumerable<ElementViewModel> Elements {get; set;}
    ...
}

请注意,当VM未实现INotifyPropertyChanged或未从实现相应DependecnyPropertyDependencyObject继承时,可能会发生内存泄漏。

那么,接下来是什么呢?多重绑定。与简单的绑定不同,MultiBinding允许您在IMultiValueConverter的帮助下将多个值转换为一个值。

我们为什么需要它?这个想法很简单,当达到所需的深度级别时,我们希望返回嵌套元素的空集合。

public class ElementsConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var elements = values[0] as IEnumerable<ElementViewModel>;
        var depth = (int)values[1];
        if (depth <= 9) // depthLimit can be passed through parameter (MultiBinding.ConverterParameter property) or via AmbientContext. Actually many ways exist.
        {
            return elements;
        }
        else
        {
            return new ElementViewModel[0];
        }
    }
    public object[] ConverBack(bject value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
<ItemsControl>
    <ItemsControl.ItemsSource>
        <MultiBinding Mode="OneWay">
            <MultiBinding.Converter>
                 <localNamespace:ElementsConverter/>
            </MultiBinding.Converter>   
            <Binding Path="Elements"/>
            <Binding Path="Depth"/>
       </MultiBinding>
    </ItemsControl.ItemsSource>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.Left" Value="{Binding Left}"/>
            <Setter Property="Canvas.Top" Value="{Binding Top}"/>
            <Setter Property="Width" Value="{Binding Width}"/>
            <Setter Property="Height" Value="{Binding Height}"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>