如何在 ComboBox.SelectedItem != null 上显示 TreeView

How to show TreeView on ComboBox.SelectedItem != null

我正在使用 MVVM 模式编写目录资源管理器。

我的 UI 由一个包含目录路径的 ComboBox 和一个用作目录浏览器的 TreeView 组成。

我一直在努力解决的问题是仅在选择 ComboBox 项目时才显示 TreeView,但我不知道如何实现。

这是我的代码。

MainWindow.xaml

<StackPanel>

        <TextBlock TextWrapping="Wrap" Text="Select a Repository Directory" Margin="10,0" FontSize="16"/>
        <ComboBox x:Name="cmbx" Margin="10,30,10,0" ItemsSource="{Binding CmbxItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>

        <TreeView x:Name="FolderView" Height="250" Margin="10,50,10,0" ItemsSource="{Binding Items}" Visibility="{Binding IsItemSelected}">

            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
                </Style>
            </TreeView.ItemContainerStyle>

            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <Image Name="img" Width="20" Margin="5" 
                                           Source="{Binding Type,
                                                Converter={x:Static local:HeaderToImageConverter.ConverterInstance}}"/>
                        <TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
                    </StackPanel>

                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>

        </TreeView>

    </StackPanel>

ViewModel.cs

/// <summary>
    /// ViewModel for the main Directory view.
    /// </summary>
    class ViewModel : BaseViewModel
    {

        #region Properties

        public ObservableCollection<DirectoryStructureViewModel> Items { get; set; }

        public ObservableCollection<string> CmbxItems { get; set; }

        public bool _itemIsSelected = false;

        public bool ItemIsSelected
        {
            get
            {
                return _itemIsSelected;
            }
            set
            {
                _itemIsSelected = value;
            }
        }

        public string _selectedItem;

        public string SelectedItem
        {
            get
            {
                return _selectedItem;
            }
            set
            {               
                ItemIsSelected = true;
                MessageBox.Show("Selection changed");
                _selectedItem = value;
            }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor.
        /// </summary>
        public ViewModel()
        {

            CmbxItems = new ObservableCollection<string>(){};

            CmbxItems.Add(DirectoryItem.rootPath);

            //Get initial directory.
            var children = DirectoryStructure.GetInitialDirectory();

            //Create view models from data.
            this.Items = new ObservableCollection<DirectoryStructureViewModel>(children.Select(dir => new DirectoryStructureViewModel(dir.FullPath, NodeTypes.Folder)));

        }

        #endregion
    }

基地ViewModel.cs

/// <summary>
    /// Base ViewModel that fires PropertyChanged events.
    /// </summary>
    public class BaseViewModel : INotifyPropertyChanged
    {
        //Event that is fired when aa child property changes its value.
        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
    }

目录结构ViewModel.cs

public class DirectoryStructureViewModel : BaseViewModel
    {
        #region Properties

        public NodeTypes Type { get; set; }

        public string FullPath { get; set; }

        public string Name { get { return DirectoryStructure.GetDirectoryOrFileName(this.FullPath); } }

        /// <summary>
        /// List of all children contained in this item.  
        /// </summary>
        public ObservableCollection<DirectoryStructureViewModel> Children { get; set; }

        /// <summary>
        /// Indicates that this item can be expanded.
        /// </summary>
        public bool CanExpand { get { return this.Type != NodeTypes.File; } }

        /// <summary>
        /// Indicates if the current item is expanded.
        /// </summary>
        public bool IsExpanded
        {
            get
            {
                return this.Children?.Count(chldrn => chldrn != null) > 0;
            }
            set
            {
                if (value == true)
                {
                    Expand();
                }
                else
                {
                    this.ClearChildren();
                }
            }
        }

        public bool IsItemSelected { get; set; }

        #endregion

        #region Commands

        public ICommand ExpandCommand { get; set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="fullPath">Path of this item.</param>
        /// <param name="type">Type of this item.</param>
        public DirectoryStructureViewModel(string fullPath, NodeTypes type)
        {
            this.ExpandCommand = new TreeViewRelayCommand(Expand);

            this.FullPath = fullPath;
            this.Type = type;

            this.ClearChildren();
        }

        #endregion

        #region Helper Methods

        //Removes all children from the list.
        private void ClearChildren()
        {
            this.Children = new ObservableCollection<DirectoryStructureViewModel>();

            if (this.Type != NodeTypes.File)
            {
                //Adds a dummy item to show the expand arrow.
                this.Children.Add(null);
            }
        }

        #endregion

        #region Functions

        /// <summary>
        /// Expands this directory and finds all children.
        /// </summary>
        private void Expand()
        {
            if (this.Type != NodeTypes.File)
            {

                //Find all children
                var children = DirectoryStructure.GetDirectoryContents(this.FullPath);
                this.Children = new ObservableCollection<DirectoryStructureViewModel>(children.Select(content => new DirectoryStructureViewModel(content.FullPath, content.Type)));
            }
            else
            {
                return;
            }
        }

        #endregion
    }

Commands.cs

class TreeViewRelayCommand : ICommand
    {
        #region Members

        private Action mAction;

        #endregion

        #region Events

        /// <summary>
        /// Event that is executed, when <see cref="CanExecute(object)"/> value has changed.
        /// </summary>
        public event EventHandler CanExecuteChanged = (sender, e) => { };

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="action"></param>
        public TreeViewRelayCommand(Action action)
        {
            mAction = action;
        }

        #endregion

        #region Command Methods

        public bool CanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        /// Executes the commands action.
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
            mAction();
        }

        #endregion

    }

编辑:我正在使用 FodyWeavers

你快到了:

<TreeView ... Visibility="{Binding IsItemSelected}">

一个问题是您将 Visibility 绑定到 boolOutputs window 中应该存在绑定错误(现在并始终检查 WPF 应用程序可能出现的各种问题)。

您可以使用 BoolToVisibilityConverter (using this answer):

<someContainer.Resources>
    <BooleanToVisibilityConverter x:Key="converter" />
</someContainer.Resources>
...
<TreeView ... Visibility="{Binding IsItemSelected, Converter={StaticResource converter}}">

支持字段不应为 public。

您应该(通常)为绑定中使用的所有属性发出通知。通常在 setter.

的末尾

我个人只会使用 getter 属性:

string _selectedItem;
public string SelectedItem
{
    get => _selectedItem;
    set
    {
        _selectedItem = value;
        OnPropertyChanged();
        OnPropertyChanged(nameof(IsItemSelected));
    }
}

public bool IsItemSelected => SelectedItem != null;

你还错过了正确的事件上升方法:

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    // can be public if you want to rise event from outside
    protected void OnPropertyChanged([CallerMemberName] string property = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));

}