【问题标题】:How to change a property of parent-viewmodel如何更改父视图模型的属性
【发布时间】:2020-05-29 07:47:30
【问题描述】:

我再次需要你的帮助。

我正在构建一个 WPF 应用程序,其中我有一个用于页面的 MainViewModel,它包含一个树视图。此树视图绑定到由 MainViewModel 创建的 ProjectTreeViewModel。

现在我的 ProjectTreeViewModel 捕获了一个单击事件(使用中继命令),它告诉它单击了哪个节点。
我在 MainViewmodel 中需要这些信息。我如何将其转移到那里?

编辑...一个可运行的示例

要在树中显示的一些数据:

using WpfApp1.Models;

namespace WpfApp1.Dataprovider
{
  class PlcAddressData
  {
    public static PlcAddress GetPlcRootItems(string projectName)
    {
        if (string.IsNullOrWhiteSpace(projectName))
            projectName = "Projekt-Datenpunkte";

        return new PlcAddress
        {
            Name = projectName,
            NodeId = 0,
            Children =
            {
                new PlcAddress
                {
                    Name = "Allgemein",
                    Comment = "allgemeine Datenpunkte",
                    NodeId = 1,
                    ParentNodeId = 0
                },
                new PlcAddress
                {
                    Name = "Infrastruktur",
                    Comment = "interne Datenpunkte der Infrastruktur",
                    ParentNodeId = 0,
                    NodeId = 2
                },
                new PlcAddress
                {
                    Name = "lokale IOs",
                    Comment = "Datenpunkte der SPS-Baugruppe",
                    ParentNodeId = 0,
                    NodeId = 3,
                    Children =
                    {
                        new PlcAddress
                        {
                            Name = "IO 0",
                            Comment = "first Channel of Plc-IO-Card",
                            NodeId = 4,
                            ParentNodeId = 3
                        },
                        new PlcAddress
                        {
                            Name = "IO 1",
                            Comment = "second Channel of Plc-IO-Card",
                            NodeId = 5,
                            ParentNodeId = 3
                        },
                        new PlcAddress
                        {
                            Name = "IO 2",
                            Comment = "third Channel of Plc-IO-Card",
                            NodeId = 6,
                            ParentNodeId = 3
                        },
                        new PlcAddress
                        {
                            Name = "IO 3",
                            Comment = "forth Channel of Plc-IO-Card",
                            NodeId = 7,
                            ParentNodeId = 3
                        },
                        new PlcAddress
                        {
                            Name = "IO 4",
                            Comment = "fifth Channel of Plc-IO-Card",
                            NodeId = 8,
                            ParentNodeId = 3
                        },
                        new PlcAddress
                        {
                            Name = "IO 5",
                            Comment = "sixth Channel of Plc-IO-Card",
                            NodeId = 9,
                            ParentNodeId = 3
                        },
                        new PlcAddress
                        {
                            Name = "IO 6",
                            Comment = "seventh Channel of Plc-IO-Card",
                            NodeId = 10,
                            ParentNodeId = 3
                        },
                        new PlcAddress
                        {
                            Name = "IO 7",
                            Comment = "eighth Channel of Plc-IO-Card",
                            NodeId = 11,
                            ParentNodeId = 3
                        }
                    }
                }
            }
        };
    }
  }
}

PlcAddress-模型(treeview-item 的数据):

using System.Collections.Generic;

namespace WpfApp1.Models
{
  public class PlcAddress
  {
    private List<PlcAddress> _children = new List<PlcAddress>();
    public List<PlcAddress> Children
    {
        get { return _children; }
        set { _children = value; }
    }

    public int NodeId { get; set; }
    public int ParentNodeId { get; set; }
    public string Name { get; set; }
    public string Comment { get; set; }
  }
}

一个RelayCommand

using System;
using System.Windows.Input;

namespace WpfApp1.ViewModels.Commands
{
  public class RelayCommand : ICommand
  {
    #region Fields
    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;
    #endregion

    #region Constructors
    public RelayCommand(Action<object> execute) : this(execute, null){ }
    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute ?? throw new ArgumentNullException("execute");
        _canExecute = canExecute;
    }
    #endregion

    #region ICommand Members
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
    public void Execute(object parameter)
    {
        _execute(parameter);
    }
    #endregion
  }
}

MainViewModel

using WpfApp1.Models;

namespace WpfApp1.ViewModels
{
  public class MainViewModel : INotifyPropertyChanged
  {
    public MainViewModel()
    {
        LoadProjectTree();

    }
    private void LoadProjectTree()
    {
        PlcAddress RootItem = Dataprovider.PlcAddressData.GetPlcRootItems("Parent Node of Project");
        _projectTree = new ProjectTreeviewModel(RootItem);
        _projectTree.PropertyChanged += ProjectTreePropertyChanged;
    }
    private void ProjectTreePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        ProjectTreeviewModel selectedNode = (ProjectTreeviewModel)sender;
        System.Console.WriteLine("selectedNode changed:" + selectedNode.SelectedNode);
        SelectedNode = selectedNode.SelectedNode;
        //MessageBox.Show("Some Property changed");
    }

    #region Properties
    private string _selectedNode;
    public string SelectedNode {
        get { return _selectedNode; } 
        set
        {
            _selectedNode = value;
            OnPropertyChanged("SelectedNode");
        }
    }
    private ProjectTreeviewModel _projectTree;
    public ProjectTreeviewModel ProjectTree
    {
        get { return _projectTree; }
    }
    #endregion

    #region Events
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
  }
}

PlcAddressViewModel 在树中显示为项目

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using WpfApp1.Models;

namespace WpfApp1.ViewModels
{
  public class PlcAddressViewModel : INotifyPropertyChanged
  {
    #region Data
    private Collection<PlcAddressViewModel> _children;
    readonly PlcAddressViewModel _parent;
    readonly PlcAddress _plcAddress;

    bool _isExpanded;
    bool _isSelected;
    #endregion Data

    #region Constructors
    public PlcAddressViewModel(PlcAddress plcAddress) : this(plcAddress, null)
    {
    }

    private PlcAddressViewModel(PlcAddress plcAddress, PlcAddressViewModel parent)
    {
        _parent = parent;
        _plcAddress = plcAddress;

        _children = new Collection<PlcAddressViewModel>(
            (from child in _plcAddress.Children
             select new PlcAddressViewModel(child, this))
             .ToList<PlcAddressViewModel>());
    }
    #endregion Constructors

    #region AddressProperties
    public Collection<PlcAddressViewModel> Children
    {
        get { return _children; }
        set { _children = value; }
    }
    public string Name
    {
        get { return _plcAddress.Name; }
    }
    public string Comment
    {
        get { return _plcAddress.Comment; }
    }
    #endregion AddressProperties

    #region Presentation Members

    #region IsExpanded
    public bool IsExpanded
    {
        get { return _isExpanded; }
        set
        {
            if (value != _isExpanded)
            {
                _isExpanded = value;
                this.OnPropertyChanged("IsExpanded");
            }

            // Expand all the way up to the root
            if (_isExpanded && _parent != null)
                _parent.IsExpanded = true;
        }
    }
    #endregion IsExpanded

    #region IsSelected
    public bool IsSelected
    {
        get
        {
            if (_isSelected)
            {
                //Console.WriteLine("Nodeselected: " + this._plcAddress.Name);
            }
            return this._isSelected;
        }
        set
        {
            if (value != _isSelected)
            {
                _isSelected = value;
                this.OnPropertyChanged("IsSelected");
            }
        }
    }
    #endregion IsSelected

    #region Parent
    public PlcAddressViewModel Parent
    {
        get { return _parent; }
    }
    #endregion Parent

    #endregion Presentation Members

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion INotifyPropertyChanged Members
  }
}

识别到 selectionchanged 的​​ ProjectTreeViewModel

using System;
using System.Collections.ObjectModel;
using WpfApp1.Models;
using WpfApp1.ViewModels.Commands;

namespace WpfApp1.ViewModels
{
  public class ProjectTreeviewModel : INotifyPropertyChanged
  {
    #region Data
    public RelayCommand TreeNodeSelected { get; private set; }
    readonly ReadOnlyCollection<PlcAddressViewModel> _rootNodes;
    readonly PlcAddressViewModel _rootAddress;
    #endregion Data

    #region Constructor
    public ProjectTreeviewModel(PlcAddress rootAddress)
    {
        _rootAddress = new PlcAddressViewModel(rootAddress);

        _rootNodes = new ReadOnlyCollection<PlcAddressViewModel>(
            new PlcAddressViewModel[]
            {
                _rootAddress
            });

        TreeNodeSelected = new RelayCommand(ExecuteTreeNodeSelected, canExecuteMethod);
    }
    #endregion Constructor

    #region Properties
    private string _selectedNode;
    public string SelectedNode
    {
        get { return _selectedNode; }
        set
        {
            _selectedNode = value;
            OnPropertyChanged("SelectedNode");
        }
    }
    #endregion

    #region RootNode
    public ReadOnlyCollection<PlcAddressViewModel> ProjectNode
    {
        get { return _rootNodes; }
    }
    #endregion RootNode

    #region Commands
    private bool canExecuteMethod(object parameter)
    {
        return true;
    }
    private void ExecuteTreeNodeSelected(object parameter)
    {
        PlcAddressViewModel selectedNode = (PlcAddressViewModel)parameter;
        Console.WriteLine("Found this node: " + selectedNode.Name);
        SelectedNode = selectedNode.Name;
    }
    #endregion Commands
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}

最后但同样重要的是,MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp1"
    xmlns:viewmodels="clr-namespace:WpfApp1.ViewModels"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800"
    >


<DockPanel LastChildFill="True">
    <StackPanel Margin="5" Orientation="Horizontal">
        <TreeView DataContext="{Binding ProjectTree}" ItemsSource="{Binding ProjectNode}" DockPanel.Dock="Left" 
              x:Name="ProjectTree" Margin="0 0 2 0" Grid.Column="0">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectedItemChanged">
                    <i:InvokeCommandAction Command="{Binding TreeNodeSelected}" 
                                       CommandParameter="{Binding ElementName=ProjectTree, Path=SelectedItem}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
                    <Setter Property="FontWeight" Value="Normal"/>
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="FontWeight" Value="Bold"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TreeView.ItemContainerStyle>

            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </StackPanel>
    <!-- following Texblock is bound to a MainViewModels property -->
    <TextBlock Text="{Binding SelectedNode}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</DockPanel>
</Window>

...及其代码隐藏

using System.Windows;
using WpfApp1.ViewModels;

namespace WpfApp1
{
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainViewModel();
    }
  }
}

...经过几个小时的实验,我解决了它。解决方案在上面的 sn-ps 中,我不知道这是否是最好的方法。我是按照以下方式完成的:

命令方法ExecuteTreeNodeSelected 更改公共属性SelectedNode。这会触发通知OnPropertyChanged("SelectedNode");

在创建 Treeviewmodel 时,在 MainViewModel 中,我向 TreeViewModel _projectTree.PropertyChanged += ProjectTreePropertyChanged; 的 PropertyChanged-event 添加了一个事件监听器。此事件更改通知 UI 的 MainViewModel 的 SelectedNode-property。

感谢您的耐心等待

【问题讨论】:

  • 你可以编写某种事件来传递它的使用事件聚合器
  • 对不起,我不知道你想告诉我什么。能否提供链接或示例?

标签: c# wpf mvvm viewmodel


【解决方案1】:

这是我用于在 TreeView 控件中显示分层数据的 ViewModel 基类。

/// <summary>
/// A base class for items that can be displayed in a TreeView or other hierarchical display
/// </summary>
public class perTreeViewItemViewModelBase : perViewModelBase
{
    // a dummy item used in lazy loading mode, ensuring that each node has at least one child so that the expand button is shown
    private static perTreeViewItemViewModelBase LazyLoadingChildIndicator { get; } 
        = new perTreeViewItemViewModelBase { Caption = "Loading Data ..." };

    private bool InLazyLoadingMode { get; set; }
    private bool LazyLoadTriggered { get; set; }
    private bool LazyLoadCompleted { get; set; }
    private bool RequiresLazyLoad => InLazyLoadingMode && !LazyLoadTriggered;

    // Has Children been overridden (e.g. to point at some private internal collection) 
    private bool LazyLoadChildrenOverridden => InLazyLoadingMode && !Equals(LazyLoadChildren, _childrenList);

    private readonly perObservableCollection<perTreeViewItemViewModelBase> _childrenList 
        = new perObservableCollection<perTreeViewItemViewModelBase>();

    /// <summary>
    /// LazyLoadingChildIndicator ensures a visible expansion toggle button in lazy loading mode 
    /// </summary>
    protected void SetLazyLoadingMode()
    {
        ClearChildren();
        _childrenList.Add(LazyLoadingChildIndicator);

        IsExpanded = false;
        InLazyLoadingMode = true;
        LazyLoadTriggered = false;
        LazyLoadCompleted = false;
    }

    private string _caption;

    public string Caption
    {
        get => _caption;
        set => Set(nameof(Caption), ref _caption, value);
    }

    public void ClearChildren()
    {
        _childrenList.Clear();
    }

    /// <summary>
    /// Add a new child item to this TreeView item
    /// </summary>
    /// <param name="child"></param>
    public void AddChild(perTreeViewItemViewModelBase child)
    {
        if (LazyLoadChildrenOverridden)
            throw new InvalidOperationException("Don't call AddChild for an item with LazyLoad mode set & LazyLoadChildren has been overridden");

        if (_childrenList.Any() && _childrenList.First() == LazyLoadingChildIndicator)
            _childrenList.Clear();

        _childrenList.Add(child);

        SetChildPropertiesFromParent(child);
    }

    protected void SetChildPropertiesFromParent(perTreeViewItemViewModelBase child)
    { 
        child.Parent = this;

        // if this node is checked then all new children added are set checked 
        if (IsChecked.GetValueOrDefault())
            child.SetIsCheckedIncludingChildren(true);

        ReCalculateNodeCheckState();
    }

    protected void ReCalculateNodeCheckState()
    {
        var item = this;

        while (item != null)
        {
            if (item.Children.Any() && !Equals(item.Children.FirstOrDefault(), LazyLoadingChildIndicator))
            {
                var hasIndeterminateChild = item.Children.Any(c => c.IsEnabled && !c.IsChecked.HasValue);

                if (hasIndeterminateChild)
                    item.SetIsCheckedThisItemOnly(null);
                else
                {
                    var hasSelectedChild = item.Children.Any(c => c.IsEnabled && c.IsChecked.GetValueOrDefault());
                    var hasUnselectedChild = item.Children.Any(c => c.IsEnabled && !c.IsChecked.GetValueOrDefault());

                    if (hasUnselectedChild && hasSelectedChild)
                        item.SetIsCheckedThisItemOnly(null);
                    else
                        item.SetIsCheckedThisItemOnly(hasSelectedChild);
                }
            }

            item = item.Parent;
        }
    }

    private void SetIsCheckedIncludingChildren(bool? value)
    {
        if (IsEnabled)
        {
            _isChecked = value;
            RaisePropertyChanged(nameof(IsChecked));

            foreach (var child in Children)
                if (child.IsEnabled)
                    child.SetIsCheckedIncludingChildren(value);
        }
    }

    private void SetIsCheckedThisItemOnly(bool? value)
    {
        _isChecked = value;
        RaisePropertyChanged(nameof(IsChecked));
    }

    /// <summary>
    /// Add multiple children to this TreeView item
    /// </summary>
    /// <param name="children"></param>
    public void AddChildren(IEnumerable<perTreeViewItemViewModelBase> children)
    {
        foreach (var child in children)
            AddChild(child);
    }

    /// <summary>
    /// Remove a child item from this TreeView item
    /// </summary>
    public void RemoveChild(perTreeViewItemViewModelBase child)
    {
        _childrenList.Remove(child);
        child.Parent = null;

        ReCalculateNodeCheckState();
    }

    public perTreeViewItemViewModelBase Parent { get; private set; }

    private bool? _isChecked = false;

    public bool? IsChecked
    {
        get => _isChecked;
        set
        {
            if (Set(nameof(IsChecked), ref _isChecked, value))
            {
                foreach (var child in Children)
                    if (child.IsEnabled)
                        child.SetIsCheckedIncludingChildren(value);

                Parent?.ReCalculateNodeCheckState();
            }
        }
    }

    private bool _isExpanded;

    public bool IsExpanded
    {
        get => _isExpanded;
        set
        {
            if (Set(nameof(IsExpanded), ref _isExpanded, value) && value && RequiresLazyLoad)
                TriggerLazyLoading();
        }
    }

    private bool _isEnabled = true;

    public bool IsEnabled
    {
        get => _isEnabled;
        set => Set(nameof(IsEnabled), ref _isEnabled, value);
    }

    public void TriggerLazyLoading()
    {
        var unused = DoLazyLoadAsync();
    }

    private async Task DoLazyLoadAsync()
    {
        if (LazyLoadTriggered)
            return;

        LazyLoadTriggered = true;

        var lazyChildrenResult = await LazyLoadFetchChildren()
            .EvaluateFunctionAsync()
            .ConfigureAwait(false);

        LazyLoadCompleted = true;

        if (lazyChildrenResult.IsCompletedOk)
        {
            var lazyChildren = lazyChildrenResult.Data;

            foreach (var child in lazyChildren)
                SetChildPropertiesFromParent(child);

            // If LazyLoadChildren has been overridden then just refresh the check state (using the new children) 
            // and update the check state (in case any of the new children is already set as checked)
            if (LazyLoadChildrenOverridden)
                ReCalculateNodeCheckState();
            else
                AddChildren(lazyChildren); // otherwise add the new children to the base collection.
        }

        RefreshChildren();
    }

    /// <summary>
    /// Get the children for this node, in Lazy-Loading Mode
    /// </summary>
    /// <returns></returns>
    protected virtual Task<perTreeViewItemViewModelBase[]> LazyLoadFetchChildren()
    {
        return Task.FromResult(new perTreeViewItemViewModelBase[0]);
    }

    /// <summary>
    /// Update the Children property
    /// </summary>
    public void RefreshChildren()
    {
        RaisePropertyChanged(nameof(Children));
    }

    /// <summary>
    /// In LazyLoading Mode, the Children property can be set to something other than
    /// the base _childrenList collection - e.g as the union ot two internal collections
    /// </summary>
    public IEnumerable<perTreeViewItemViewModelBase> Children => LazyLoadCompleted
                                                                ? LazyLoadChildren
                                                                : _childrenList;

    /// <summary>
    /// How are the children held when in lazy loading mode.
    /// </summary>
    /// <remarks>
    /// Override this as required in descendent classes - e.g. if Children is formed from a union
    /// of multiple internal child item collections (of different types) which are populated in LazyLoadFetchChildren()
    /// </remarks>
    protected virtual IEnumerable<perTreeViewItemViewModelBase> LazyLoadChildren => _childrenList;

    private bool _isSelected;

    public bool IsSelected
    {
        get => _isSelected;
        set
        {
            // if unselecting we don't care about anything else other than simply updating the property
            if (!value)
            {
                Set(nameof(IsSelected), ref _isSelected, false);
                return;
            }

            // Build a priority queue of operations
            //
            // All operations relating to tree item expansion are added with priority = DispatcherPriority.ContextIdle, so that they are
            // sorted before any operations relating to selection (which have priority = DispatcherPriority.ApplicationIdle).
            // This ensures that the visual container for all items are created before any selection operation is carried out.
            //
            // First expand all ancestors of the selected item - those closest to the root first
            //
            // Expanding a node will scroll as many of its children as possible into view - see perTreeViewItemHelper, but these scrolling
            // operations will be added to the queue after all of the parent expansions.
            var ancestorsToExpand = new Stack<perTreeViewItemViewModelBase>();

            var parent = Parent;
            while (parent != null)
            {
                if (!parent.IsExpanded)
                    ancestorsToExpand.Push(parent);

                parent = parent.Parent;
            }

            while (ancestorsToExpand.Any())
            {
                var parentToExpand = ancestorsToExpand.Pop();
                perDispatcherHelper.AddToQueue(() => parentToExpand.IsExpanded = true, DispatcherPriority.ContextIdle);
            }

            // Set the item's selected state - use DispatcherPriority.ApplicationIdle so this operation is executed after all
            // expansion operations, no matter when they were added to the queue.
            //
            // Selecting a node will also scroll it into view - see perTreeViewItemHelper
            perDispatcherHelper.AddToQueue(() => Set(nameof(IsSelected), ref _isSelected, true), DispatcherPriority.ApplicationIdle);

            // note that by rule, a TreeView can only have one selected item, but this is handled automatically by 
            // the control - we aren't required to manually unselect the previously selected item.

            // execute all of the queued operations in descending DispatcherPriority order (expansion before selection)
            var unused = perDispatcherHelper.ProcessQueueAsync();
        }
    }

    public override string ToString()
    {
        return Caption;
    }

    /// <summary>
    /// What's the total number of child nodes beneath this one
    /// </summary>
    public int ChildCount => Children.Count() + Children.Sum(c => c.ChildCount);
}

除其他功能外,它还包括一个 Parent 属性,因此数据结构中的每个项目都知道它的直接祖先。

我没有使用事件来处理被单击的项目,而是创建了一个行为来将可绑定的选定项目属性添加到 TreeView。这允许通过直接鼠标单击以外的方式检测选择更改,例如用户按下箭头键。

public class perTreeViewHelper : Behavior<TreeView>
{
    public object BoundSelectedItem
    {
        get => GetValue(BoundSelectedItemProperty);
        set => SetValue(BoundSelectedItemProperty, value);
    }

    public static readonly DependencyProperty BoundSelectedItemProperty =
        DependencyProperty.Register("BoundSelectedItem",
            typeof(object),
            typeof(perTreeViewHelper),
            new FrameworkPropertyMetadata(null,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                OnBoundSelectedItemChanged));

    private static void OnBoundSelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (args.NewValue is perTreeViewItemViewModelBase item)
            item.IsSelected = true;
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
        base.OnDetaching();
    }

    private void OnTreeViewSelectedItemChanged(object obj, RoutedPropertyChangedEventArgs<object> args)
    {
        BoundSelectedItem = args.NewValue;
    }
}

关于我的blog post的更多详细信息

【讨论】:

  • 感谢您的回答,我还没有阅读您的博客,但我必须澄清一下,我并不是说父节点是单击树节点的父节点。我的意思是 MainViewmodel,它包含 Treeview 和其他控件的 Viewmodel。我的问题是,我不知道我的 MainViewmodel 如何获知点击了哪个节点。 ...现在我要去读你的博客:)
  • 如果您按照我的回答中所述实现绑定的选定项属性,则可以将其绑定到主视图模型上的属性。
  • 对不起,如果我不得不像个菜鸟一样问...这正是我的知识差距。在我的 Treeviewmodel 中,我有一个触发的命令。从这一点开始,我有一个参数,它保存选定的节点。如何在此视图模型之外传输(绑定)它?
  • 我不明白你的 ParentViewModel 类的目的。查看我的博客文章 github.com/Peregrine66/PeregrinesView/tree/master/blog/… 的演示项目 - MainViewModel 包含 perTreeViewItemViewModelBase 后代 (RootItemVms) 的集合,这些后代 (RootItemVms) 绑定到 TreeView 控件的 ItemsSource 属性以及通过行为类绑定的 SelectedItem 属性到 TreeView 中的选定项目。
  • 对不起,这对我来说太多了。我创建了一个可运行的项目,并将在上面编辑我的问题。
猜你喜欢
  • 2014-01-12
  • 1970-01-01
  • 2017-09-07
  • 2017-05-04
  • 1970-01-01
  • 1970-01-01
  • 2016-10-20
  • 2013-05-11
  • 2013-08-26
相关资源
最近更新 更多