当项目被添加到绑定的可观察集合时,WPF树形视图可见性转换器不更新。

问题描述 投票:0回答:1

我已经构建出一个绑定到可观察集合的树形视图,并且在每个树形视图项之间建立了连接线。正在使用的视图模型实现了INotifyPropertyChanged,我使用PropertyChanged.Fody进行编织。树形视图绑定到集合上,并且更新得很好,除了一件事。当我在运行时向列表中添加一个新项目时,UI似乎不能正常更新。我已经尝试了所有在网上搜索到的关于如何在添加一个根项时强制更新UI而不需要发送命令重建整个树的方法,这确实有效,但一定还有其他方法我没有找到。

我正在使用Ninject进行依赖注入。

我把所有的代码放在我的问题下面,供参考。同样,这一切都很好,直到在运行时将一个项目添加到集合中。项目被添加到集合后,在树形视图中可见,但最后一行转换器并没有正确更新所有的图形。

考虑到下图。

Tree view after a root level item is added

一旦添加了一个项目,现在变成倒数第二的节点 他的连接线可见性没有更新 他仍然认为他是分支上的最后一个。我试过了所有能找到的UI刷新方法类型,都没有用。我在这里错过了一些东西,但对WPF相当陌生。如果有人能提供任何建议,我将非常感激。谢谢!我最初是这样做的。

下面是我最初构建树形视图的方法,效果很好。

ProjectHelpers.JsonObject = JObject.Parse(File.ReadAllText(ProjectPath.BaseDataFullPath));

//-- Get the channels, which are the top level tree elements
var children = ProjectHelpers.GetChannels();

//-- add the channels to the application channel collection
IoC.Application.Channels = new ObservableCollection<ProjectTreeItemViewModel>();

foreach(var c in children)
    IoC.Application.Channels.Add(new ProjectTreeItemViewModel(c.Path, ProjectItemType.Channel));

它包含在这个类中


    /// <summary>
    /// The view model for the main project tree view
    /// </summary>
    public class ProjectTreeViewModel : BaseViewModel
    {

        /// <summary>
        /// Name of the image displayed above the tree view UI
        /// </summary>
        public string RootImageName => "blink";

        /// <summary>
        /// Default constructor
        /// </summary>
        public ProjectTreeViewModel()
        {
            BuildProjectTree();
        }

        #region Handlers : Building project data tree

        /// <summary>
        /// Builds the entire project tree
        /// </summary>
        public void BuildProjectTree()
        {

            ProjectHelpers.JsonObject = JObject.Parse(File.ReadAllText(ProjectPath.BaseDataFullPath));

            //-- Get the channels, which are the top level tree elements
            var children = ProjectHelpers.GetChannels();

            //-- add the channels to the application channel collection
            IoC.Application.Channels = new ObservableCollection<ProjectTreeItemViewModel>();

            foreach(var c in children)
                IoC.Application.Channels.Add(new ProjectTreeItemViewModel(c.Path, ProjectItemType.Channel));               
        }

        #endregion
    }

视图模型的项目被添加到可观察的集合中。


    /// <summary>
    /// The view model that represents an item within the tree view
    /// </summary>
    public class ProjectTreeItemViewModel : BaseViewModel
    {
        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="path">The JSONPath for the item</param>
        /// <param name="type">The type of project item type</param>
        public ProjectTreeItemViewModel(string path = "", ProjectItemType type = ProjectItemType.Channel)
        {
            //-- Create commands
            ExpandCommand = new RelayCommand(Expand);
            GetNodeDataCommand = new RelayCommand(GetNodeData);

            FullPath = path;
            Type = type;

            //-- Setup the children as needed
            ClearChildren();
        }

        #region Public Properties

        /// <summary>
        /// The JSONPath for this item
        /// </summary>
        public string FullPath { get; set; }

        /// <summary>
        /// The type of project item
        /// </summary>
        public ProjectItemType Type { get; set; }

        /// <summary>
        /// Gets and sets the image name associated with project tree view headers.
        /// </summary>
        public string ImageName
        {
            get
            {
                switch (Type)
                {
                    case ProjectItemType.Channel:
                        return "channel";

                    case ProjectItemType.Device:
                        return "device";

                    default:
                        return "blink";

                }
            }
        }

        /// <summary>
        /// Gets the name of the item as a string
        /// </summary>
        public string Name => ProjectHelpers.GetPropertyValue(FullPath, "Name");

        /// <summary>
        /// Gets the associated driver as a string
        /// </summary>
        public string Driver => ProjectHelpers.GetPropertyValue(FullPath, "Driver");

        /// <summary>
        /// A list of all children contained inside this item
        /// </summary>
        public ObservableCollection<ProjectTreeItemViewModel> Children { get; set; }

        /// <summary>
        /// Indicates if this item can be expanded
        /// </summary>
        public bool CanExpand => (Type != ProjectItemType.Device);

        /// <summary>
        /// Indicates that the tree view item is selected, bound to the UI
        /// </summary>
        public bool IsSelected { get; set; }

        /// <summary>
        /// Indicates if the current item is expanded or not
        /// </summary>
        public bool IsExpanded
        {
            get {
                return (Children?.Count(f => f != null) >= 1);
            }
            set {
                //-- If the UI tells us to expand...
                if (value == true)
                    //-- Find all children
                    Expand();
                //-- If the UI tells us to close
                else
                    this.ClearChildren();
            }
        }

        #endregion


        #region Commands

        /// <summary>
        /// The command to expand this item
        /// </summary>
        public ICommand ExpandCommand { get; set; }

        /// <summary>
        /// Command bound by left mouse click on tree view item
        /// </summary>
        public ICommand GetNodeDataCommand { get; set; }

        #endregion


        #region Public Methods

        /// <summary>
        /// Expands a tree view item
        /// </summary>
        public void Expand()
        {
            //-- return if we are either a device or already expanded
            if (this.Type == ProjectItemType.Device || this.IsExpanded == true)
                return;

            //-- find all children
            var children = ProjectHelpers.GetChildrenByName(FullPath, "Devices");
            this.Children = new ObservableCollection<ProjectTreeItemViewModel>(
                            children.Select(c => new ProjectTreeItemViewModel(c.Path, ProjectHelpers.GetItemType(FullPath))));
        }

        /// <summary>
        /// Clears all children of this node
        /// </summary>
        public void ClearChildren()
        {
            //-- Clear items
            this.Children = new ObservableCollection<ProjectTreeItemViewModel>();

            //-- Show the expand arrow if we are not a device
            if (this.Type != ProjectItemType.Device)
                this.Children.Add(null);
        }

        /// <summary>
        /// Clears the children and expands it if it has children
        /// </summary>
        public void Reset()
        {
            this.ClearChildren();

            if (this.Children?.Count > 0)
                this.Expand();
        }

        #endregion


        #region Public Methods

        /// <summary>
        /// Shows the view model data in the node context data grid
        /// </summary>
        public void GetNodeData()
        {
            switch (Type)
            {
                //-- get the devices associated with that channel
                case ProjectItemType.Channel:
                    IoC.Application.UpdateDeviceDataContext(FullPath);
                    break;

                //-- get the tags associated with that device
                case ProjectItemType.Device:
                    IoC.Application.UpdateTagDataContext(FullPath);
                    break;
            }
        }

        #endregion
    }

这是我的树形视图项目的模板。


<Style x:Key="BaseTreeViewItemTemplate" TargetType="{x:Type TreeViewItem}">
    <Setter Property="Panel.ZIndex" Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource TreeViewItemZIndexConverter}}" />
    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderBrush" Value="Black" />
    <Setter Property="Padding" Value="1,2,2,2"/>

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TreeViewItem}">
                <Grid Name="ItemRoot">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="20"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>

                    <Grid Name="Lines" Grid.Column="0" Grid.Row="0">
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>

                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>

                        <!-- L shape -->
                        <Border Grid.Row="0" Grid.Column="1" Name="TargetLine" BorderThickness="1 0 0 1" SnapsToDevicePixels="True" BorderBrush="Red"/>

                        <!-- line that follows a tree view item -->
                        <Border Name="LineToNextItem"
                                Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TreeLineVisibilityConverter}}"
                                Grid.Row="1" Grid.Column="1" BorderThickness="1 0 0 0" SnapsToDevicePixels="True" BorderBrush="Blue"/>
                    </Grid>

                    <ToggleButton x:Name="Expander" Grid.Column="0" Grid.Row="0"
                              Style="{StaticResource ExpandCollapseToggleStyle}" 
                              IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" 
                              ClickMode="Press"/>

                    <!-- selected border background -->
                    <Border Name="ContentBorder" Grid.Column="1" Grid.Row="0"
                        HorizontalAlignment="Left"
                        Background="{TemplateBinding Background}" 
                        BorderBrush="{TemplateBinding BorderBrush}" 
                        BorderThickness="{TemplateBinding BorderThickness}" 
                        Padding="{TemplateBinding Padding}" 
                        SnapsToDevicePixels="True">
                        <ContentPresenter x:Name="ContentHeader" ContentSource="Header" MinWidth="20"/>
                    </Border>

                    <Grid Grid.Column="0" Grid.Row="1">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>

                        <Border BorderThickness="1 0 0 0"
                                Name="TargetBorder"
                                Grid.Column="1"
                                SnapsToDevicePixels="True"
                                BorderBrush="Olive"
                                Visibility="{Binding ElementName=LineToNextItem, Path=Visibility}"
                                />
                    </Grid>

                    <ItemsPresenter x:Name="ItemsHost" Grid.Column="1" Grid.Row="1" />
                </Grid>

                <ControlTemplate.Triggers>

                    <Trigger Property="HasItems" Value="false">
                        <Setter TargetName="Expander" Property="Visibility" Value="Hidden"/>
                    </Trigger>
                    <Trigger Property="IsExpanded" Value="false">
                        <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
                    </Trigger>

                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="HasHeader" Value="False"/>
                            <Condition Property="Width" Value="Auto"/>
                        </MultiTrigger.Conditions>
                        <Setter TargetName="ContentHeader" Property="MinWidth" Value="75"/>
                    </MultiTrigger>

                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="HasHeader" Value="False"/>
                            <Condition Property="Height" Value="Auto"/>
                        </MultiTrigger.Conditions>
                        <Setter TargetName="ContentHeader" Property="MinHeight" Value="19"/>
                    </MultiTrigger>

                    <Trigger Property="IsEnabled" Value="True">
                        <Setter Property="Foreground" Value="{StaticResource OffWhiteBaseBrush}"/>
                    </Trigger>

                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="True"/>
                            <Condition Property="IsSelectionActive" Value="True"/>
                            </MultiTrigger.Conditions>
                        <Setter TargetName="ContentBorder" Property="Background" Value="{StaticResource SelectedTreeViewItemColor}"/>
                        <Setter Property="Foreground" Value="White" />
                    </MultiTrigger>

                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

我的自定义树形视图控件


<UserControl ...>
    <UserControl.Template>
        <ControlTemplate TargetType="UserControl">

            <StackPanel Background="Transparent"
                        Margin="8"
                        Orientation="Vertical"
                        VerticalAlignment="Top"
                        HorizontalAlignment="Left"
                        TextBlock.TextAlignment="Left">

                <Image x:Name="Root"
                       ContextMenuOpening="OnContextMenuOpened"
                       Width="18" Height="18"
                       HorizontalAlignment="Left"
                       RenderOptions.BitmapScalingMode="HighQuality"
                       Margin="2.7 0 0 3"
                       Source="{Binding RootImageName, Converter={x:Static local:HeaderToImageConverter.Instance}}" />

                <TreeView Name="ProjectTreeView"
                          Loaded="OnTreeViewLoaded"
                          SelectedItemChanged="OnTreeViewSelectedItemChanged"
                          ContextMenuOpening="OnContextMenuOpened"
                          BorderBrush="Transparent"
                          Background="Transparent"
                          VirtualizingStackPanel.IsVirtualizing="True"
                          VirtualizingStackPanel.VirtualizationMode="Recycling"
                          Style="{StaticResource ResourceKey=BaseTreeViewTemplate}"
                          ItemContainerStyle="{StaticResource ResourceKey=BaseTreeViewItemTemplate}"
                          ItemsSource="{Binding ApplicationViewModel.Channels, Source={x:Static local:ViewModelLocator.Instance}}">

                    <TreeView.ContextMenu>
                        <ContextMenu>
                            <MenuItem Header="New Item" />
                            <MenuItem Header="Cut" />
                            <MenuItem Header="Copy" />
                            <MenuItem Header="Delete" />
                            <MenuItem Header="Diagnostics" />
                            <MenuItem Header="Properties" />
                        </ContextMenu>
                    </TreeView.ContextMenu>

                    <TreeView.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
                            <StackPanel Orientation="Horizontal" Margin="2">
                                <Image Width="15" Height="15" RenderOptions.BitmapScalingMode="HighQuality"
                                        Margin="-1 0 0 0"
                                        Source="{Binding Path=ImageName, Converter={x:Static local:HeaderToImageConverter.Instance}}" />

                                <TextBlock Margin="6,2,2,0" VerticalAlignment="Center" Text="{Binding Path=Name}" />
                            </StackPanel>
                        </HierarchicalDataTemplate>
                    </TreeView.ItemTemplate>
                </TreeView>

                <ContentPresenter />

            </StackPanel>
        </ControlTemplate>
    </UserControl.Template>
</UserControl>

树形视图模板中连接线的可见性转换。


    /// <summary>
    /// Visibility converter for a connecting line inside the tree view UI
    /// </summary>
    public class TreeLineVisibilityConverter : BaseValueConverter<TreeLineVisibilityConverter>
    {
        public override object Convert(object value, Type targetType = null, object parameter = null, CultureInfo culture = null)
        {
            TreeViewItem item = (TreeViewItem)value;
            ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);

            bool isLastItem = (ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1);
            return isLastItem ? Visibility.Hidden : Visibility.Visible;
        }

        public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

c# wpf data-binding treeview treeviewitem
1个回答
1
投票

问题的存在是由于这个绑定。

Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TreeLineVisibilityConverter}}"

你绑定的是项目容器本身。这个值永远不会改变,因此 Binding 只在模板被应用到容器时触发一次。

您应该绑定到一个也会改变的属性,每当 ItemsSource 变化。我认为最好的解决方案是把这个逻辑移到项目或转换器上。

为了这个目的,我已经添加了一个 IsLast 属性到数据模型 ProjectTreeItemViewModel 必需提高 INotifyPropertyChanged.PropertyChanged 的变化。此属性的初始默认值应该是 false.

边框的可见性与此属性绑定,使用您现有的,但修改后的 TreeLineVisibilityConverter.

转换器必须变成一个 IMultiValueConverter 因为我们需要绑定到新的 ProjectTreeItemViewModel.IsLast 并使用 MultiBinding.

每当有新项目添加到 TreeView它的模板将被加载。这将触发 MultiBinding 所以 IMultiValueConverter. 转换器会检查当前的项目是否是最后一个。如果是,他将

  1. 设置前一个项目 ProjectTreeItemViewModel.IsLastfalse,这将重新触发 MultiBinding 为前一个项目显示行。

  2. 设置当前的 ProjectTreeItemViewModel.IsLasttrue.

  3. 返回相应的 Visibility.

TreeLineVisibilityConverter.cs)。

public class TreeLineVisibilityConverter : IMultiValueConverter
{
  public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
  {
    TreeViewItem item = (TreeViewItem) values[0];
    ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
    int lastIndex = ic.Items.Count - 1;

    bool isLastItem = (ic.ItemContainerGenerator.IndexFromContainer(item) == lastIndex);
    if (isLastItem)
    {
      ResetIsLastOfPrevousItem(ic.Items.Cast<ProjectTreeItemViewModel>(), lastIndex);
      (item.DataContext as ProjectTreeItemViewModel).IsLast = true;
    }

    return isLastItem 
      ? Visibility.Hidden 
      : Visibility.Visible;
  }

  private void ConvertBack(IEnumerable<ProjectTreeItemViewModel> items, int lastIndex)
  {
    ProjectTreeItemViewModel previousItem = items.ElementAt(lastIndex - 1);
    if (previousItem.IsLast && items.Count() > 1)
    {
      previousItem.IsLast = false;
    }
  }

  public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    throw new NotSupportedException();
  }
}

ControlTemplateTreeViewItem

<ControlTemplate TargetType="TreeViewItem">
  ...

  <!-- line that follows a tree view item -->
  <Border Name="LineToNextItem">
    <Border.Visibility>
      <MultiBinding Converter="{StaticResource TreeLineVisibilityConverter}">
        <Binding RelativeSource="{RelativeSource TemplatedParent}"/>
        <Binding Path="IsLast" />
      </MultiBinding>
    </Border.Visibility>
  </Border>

  ...
</ControlTemplate>

备注

出于性能考虑,你应该考虑增加一个 Parent 财产给你 ProjectTreeItemViewModel. 遍历模型树比遍历视觉树更有效率。然后在你的 ControlTemplate 您只需简单地更换绑定到 TemplatedParent (TreeViewItem),并结合 DataContextControlTemplate 例如:, {Binding} (或 <Binding /> 若是 MultiBinding),它将返回当前的 ProjectTreeItemViewModel. 在这里,你可以通过访问的 ProjectTreeItemViewModel.Children 财产,通过 ProjectTreeItemViewModel.Parent. 这样你就不用再使用 ItemContainerGenerator 的项目,而不需要投下 ItemsControl.ItemsIEnumerable<ProjectTreeItemViewModel>.



MVVM树形视图示例

这是一个关于如何使用MVVM构建树的简单例子。这个例子假装从一个文本文件创建数据树。请看 ProjectTreeItem 类来查看如何使用递归来遍历树,例如。GetTreeRoot().

在最后,还有一个修订版的 TreeLineVisibilityConverter 来展示你如何通过使用 Parent 参考 static 属性)。)

ProjectTreeItem.cs

// The data view model of the tree items.
// Since this is the binding source of the TreeView,
// this class should implement INotifyPropertyChanged.
// This classes property setters are simplified.
public class ProjectTreeItem : INotifyPropertyChanged
{
  /// <summary>
  /// Default constructor
  /// </summary>
  public ProjectTreeItem(string data)
  {
    this.Data = data;
    this.Parent = null;
    this.Children = new ObservableCollection<ProjectTreeItem>();
  }

  // Traverse tree and expand subtree.
  public ExpandChildren()
  {
    foreach (var child in this.Children)
    {
      child.IsExpanded = true;
      child.ExpandChildren();
    }
  }

  // Traverse complete tree and expand each item.
  public ExpandTree()
  {
    // Get the root of the tree
    ProjectTreeItem rootItem = GetTreeRoot(this);
    foreach (var child in rootItem.Children)
    {
      child.IsExpanded = true;
      child.ExpandChildren();
    }
  }

  // Traverse the tree to the root using recursion.
  private ProjectTreeItem GetTreeRoot(ProjectTreeItem treeItem)
  {
    // Check if item is the root
    if (treeItem.Parent == null)
    {
      return treeItem;
    }

    return GetTreeRoot(treeItem.Parent);
  }

  public string Data { get; set; }
  public bool IsExpanded { get; set; }
  public ProjectTreeItem Parent { get; set; }
  public ObservableCollection<ProjectTreeItem> Children { get; set; }
}

Repository.cs

// A model class in the sense of MVVM
public class Repository
{
  public ProjectTreeItem ReadData()
  {
    var lines = File.ReadAllLines("/path/to/data");

    // Create the tree structure from the file data
    return CreateDataModel(lines);
  }

  private ProjectTreeItem CreateDataModel(string[] lines)
  {
    var rootItem = new ProjectTreeItem(string.Empty);

    // Pretend each line contains tokens separated by a whitespace,
    // then each line is a parent and the tokens its children.
    // Just to show how to build the tree by setting Parent and Children.
    foreach (string line in lines)
    {
      rootItem.Children.Add(CreateNode(line));
    }

    return rootItem;
  }

  private ProjectTreeItem CreateNode(string line)
  {
    var nodeItem = new ProjectTreeItem(line);
    foreach (string token in line.Split(' '))
    {
      nodeItem.Children.Add(new ProjectTreeItem(token) {Parent = nodeItem});
    }

    return nodeItem;
  }
}

DataController.cs

// Another model class in the sense of MVVM
public class DataController
{
  public DataController()
  {
    // Create the model. Alternatively use constructor 
    this.Repository = new Repository();
  }

  public IEnumerable<ProjectTreeItem> GetData()
  {
    return this.Repository.ReadData().Children;
  }

  private Repository Repository { get; set; }
}

主视图模型.cs

// The data view model of the tree items.
// Since this is a binding source of the view,
// this class should implement INotifyPropertyChanged.
// This classes property setters are simplified.
public class MainViewModel : INotifyPropertyChanged
{
  public MainViewModel()
  {
    // Create the model. Alternatively use constructor injection.
    this.DataController = new DataController();
    Initialize();
  }

  private void Initialize()
  {
    IEnumerable<ProjectTreeItem> treeData = this.DataController.GetData();
    this.TreeData = new ObservableCollection<ProjectTreeItem>(treeData);
  }

  public ObservableCollection<ProjectTreeItem> TreeData { get; set; }

  private DataController DataController { get; set; }
}

TreeLineVisibilityConverter.cs。

public class TreeLineVisibilityConverter : IMultiValueConverter
{
  public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
  {
    ProjectTreeItem item = values[0] as ProjectTreeItem;

    // If current item is root return
    if (item.Parent == null)
    {
      return Binding.DoNothing;
    }

    ProjectTreeItem parent = item?.Parent ?? item;
    int lastIndex = item.Parent.Chilidren.Count - 1;

    bool isLastItem = item.Parent.Chilidren.IndexOf(item) == lastIndex);
    if (isLastItem)
    {
      ResetIsLastOfPrevousItem(item.Parent.Chilidren, lastIndex);
      item.IsLast = true;
    }

    return isLastItem 
      ? Visibility.Hidden 
      : Visibility.Visible;
  }

  private void ConvertBack(IEnumerable<ProjectTreeItem> items, int lastIndex)
  {
    ProjectTreeItem previousItem = items.ElementAt(lastIndex - 1);
    if (previousItem.IsLast && items.Count() > 1)
    {
      previousItem.IsLast = false;
    }
  }

  public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    throw new NotSupportedException();
  }
}

UserControl.xaml

<UserControl>
  <UserControl.DataContext>
    <MainViewModel />
  <UserControl.DataContext>

  <UserControl.Resources>
    <ControlTemplate TargetType="TreeViewItem">
      ...

      <!-- line that follows a tree view item -->
      <Border Name="LineToNextItem">
        <Border.Visibility>
          <MultiBinding Converter="{StaticResource TreeLineVisibilityConverter}">
            <Binding />
            <Binding Path="IsLast" />
          </MultiBinding>
        </Border.Visibility>
      </Border>

      ...
    </ControlTemplate>
  <UserControl.Resources>

  <TreeView ItemsSource="{Binding TreeData}" />
</UserControl>

1
投票

在这里感谢@BionicCode的帮助,意义重大。我想分享一下我对视图模型遍历的实现,以代替可视化树的遍历。我最终没有在ProjectTreeItemViewModel类中创建一个字段来引用父容器,而是创建了一个ParentIndex和一个ChildIndex,让我可以通过引用FullPath属性来快速访问我需要的项目,FullPath只是json内容的JSONPath。说实话,我不太清楚你是如何在类中包含对父容器的引用的,但想看看你建议的实现方式。再次感谢@BionicCode,祝你周末愉快!

这是我现在的转换器。


    /// <summary>
    /// Visibility converter for the connecting lines on the tree view UI
    /// </summary>
    public class ConnectingLineVisibilityConverter : IMultiValueConverter
    {
        /// <summary>
        /// Returns the proper visibility according to location on the tree view UI
        /// </summary>
        public object Convert(object[] values, Type targetType = null, object parameter = null, CultureInfo culture = null)
        {
            ProjectTreeItemViewModel viewModel = (ProjectTreeItemViewModel)values[0];

            //-- collection context by default is the channels
            var collection = IoC.Application.Channels;
            int currentIndex = viewModel.ParentIndex;

            if (viewModel.Type == ProjectItemType.Device) {
                //-- change the collection context to the children of this channel
                collection = collection[currentIndex].Children;
                currentIndex = viewModel.ChildIndex;
            }

            int lastIndex = collection.Count - 1;
            bool isLastItem = (currentIndex == lastIndex);

            //-- is it the last of it's branch?
            if (isLastItem) {
                ResetPreviousSibling(collection, lastIndex);
                viewModel.IsLast = true;
            }

            return isLastItem ? Visibility.Hidden : Visibility.Visible;
        }

        /// <summary>
        /// Resets the previous sibling IsLast flag once a new item is added to the collection
        /// </summary>
        /// <param name="collection">The collection to search</param>
        /// <param name="lastIndex">The index of the previous sibling</param>
        private void ResetPreviousSibling(ObservableCollection<ProjectTreeItemViewModel> collection, int lastIndex)
        {
            //-- there's only one item in the collection
            if (lastIndex == 0)
                return;

            //-- get the previous sibling and reset it's IsLast flag, if necessary
            ProjectTreeItemViewModel previousSibling = collection[lastIndex - 1];
            if (previousSibling.IsLast)
                previousSibling.IsLast = false;
        }

        public object[] ConvertBack(object value, Type[] targetTypes = null, object parameter = null, CultureInfo culture = null)
        {
            throw new NotImplementedException();
        }
    }

然后,绑定变成...

    <!-- connecting line to the next item -->
    <Border Name="LineToNextItem" Grid.Row="1" Grid.Column="1" BorderThickness="1 0 0 0" SnapsToDevicePixels="True" BorderBrush="Blue">
        <Border.Visibility>
            <MultiBinding Converter="{StaticResource ConnectingLineVisibilityConverter}">
                <Binding />
                <Binding Path="IsLast" />
            </MultiBinding>
        </Border.Visibility>
    </Border>

© www.soinside.com 2019 - 2024. All rights reserved.