根据 CheckComboBox 中的选择动态 hide/show 属性

Dynamically hide/show properties based on the selection in a CheckComboBox

在 WPF 中,我希望有这样一个视图模型,在 PropertyGrid 中它应该显示一个 CheckComboBox 使我能够动态地 show/hide 其他属性(基于选择)。

我用以下 属性 的内容填充 CheckComboBox

// A collection used as the data source for the CheckComboBox.
[RefreshProperties(RefreshProperties.Repaint)]
public ObservableCollection<TriggerTypeItem> TriggerTypes { get; } = new ObservableCollection<TriggerTypeItem>();

其中 TriggerTypeItem 实现了 INotifyPropertyChanged 接口。

但是,似乎对 TriggerTypeItem 元素的更改不会导致 TriggerTypes 属性被视为已修改,因此 - Browsable 属性的动态更改未反映在属性 网格。
SetBrowsableAttribute() 函数工作正常,您可以通过切换 ShouldShow 属性 的复选框看到。)

我应该怎么做才能达到预期的效果?

MainWindow.xaml

<Window x:Class="WpfPlayground.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="300">
    <Grid>
        <xctk:PropertyGrid x:Name="PropertyGridControl">
            <xctk:PropertyGrid.EditorDefinitions>
                <xctk:EditorTemplateDefinition TargetProperties="TriggerTypes">
                    <xctk:EditorTemplateDefinition.EditingTemplate>
                        <DataTemplate>
                            <xctk:CheckComboBox ItemsSource="{Binding Instance.TriggerTypes}"
                                                DisplayMemberPath="TriggerType"
                                                SelectedMemberPath="Selected" />
                        </DataTemplate>
                    </xctk:EditorTemplateDefinition.EditingTemplate>
                </xctk:EditorTemplateDefinition>
            </xctk:PropertyGrid.EditorDefinitions>
        </xctk:PropertyGrid>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows;

namespace WpfPlayground
{
    public partial class MainWindow : Window
    {
        private readonly EventViewModel eventViewModel = new EventViewModel();

        public MainWindow()
        {
            InitializeComponent();
            PropertyGridControl.SelectedObject = eventViewModel;
        }
    }

    public abstract class PropertyChangedBase : Component, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void NotifyOfPropertyChange([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public enum TriggerType
    {
        Timer,
    }

    public class EventViewModel : PropertyChangedBase
    {
        private bool _shouldShow;
        private readonly Dictionary<TriggerType, string> _triggerViewModels = new Dictionary<TriggerType, string>()
        {
            { TriggerType.Timer, nameof(ToogleProperty) }
        };

        // A "regular" checkbox for toogling.
        [RefreshProperties(RefreshProperties.Repaint)]
        public bool ShouldShow
        {
            get { return _shouldShow; }
            set
            {
                TypeDescriptor.GetProperties(this)[nameof(ToogleProperty)].SetBrowsableAttribute(value);

                _shouldShow = value;
                NotifyOfPropertyChange(nameof(ShouldShow));
            }
        }

        // A collection used as the data source for the CheckComboBox.
        [RefreshProperties(RefreshProperties.Repaint)]
        public ObservableCollection<TriggerTypeItem> TriggerTypes { get; } = new ObservableCollection<TriggerTypeItem>();

        // The property to be toogled.
        [ReadOnly(true)]
        public string ToogleProperty { get; set; } = "(Toggle me!)";

        public EventViewModel()
        {
            ShouldShow = true;
            foreach (TriggerType val in Enum.GetValues(typeof(TriggerType)))
            {
                TriggerTypes.Add(new TriggerTypeItem(triggerType: val, eventModel: this) { Selected = false });
            }
        }

        /// <summary>
        /// Dynamically sets the <c>Browsable</c> attribute.
        /// </summary>
        public void UpdatePropertyGridTriggers(TriggerType triggerType, bool newBrowsableState)
        {
            if (_triggerViewModels.TryGetValue(triggerType, out string propertyName))
            {
                TypeDescriptor.GetProperties(this)[propertyName].SetBrowsableAttribute(newBrowsableState);
                NotifyOfPropertyChange(nameof(propertyName));
            }
        }
    }

    /// <summary>
    /// An item in the CheckCoboBox.
    /// </summary>
    public class TriggerTypeItem : PropertyChangedBase
    {
        private TriggerType _triggerType;
        private bool _selected;

        public EventViewModel EventModel { get; private set; }

        public TriggerType TriggerType
        {
            get { return _triggerType; }
            set
            {
                _triggerType = value;
                NotifyOfPropertyChange(nameof(TriggerType));
            }
        }

        public bool Selected
        {
            get { return _selected; }
            set
            {
                if (_selected != value)
                {
                    _selected = value;
                    EventModel?.UpdatePropertyGridTriggers(TriggerType, value);
                    NotifyOfPropertyChange(nameof(Selected));
                }
            }
        }

        public TriggerTypeItem(TriggerType triggerType, EventViewModel eventModel)
        {
            EventModel = eventModel;
            TriggerType = triggerType;
        }
    }

    public static class PropertyDescriptorExtensions
    {
        /// See: http://www.reza-aghaei.com/make-a-property-read-only-in-propertygrid/ (Solution #2)
        public static void SetBrowsableAttribute(this PropertyDescriptor p, bool value)
        {
            var attributes = p.Attributes.Cast<Attribute>().Where(x => !(x is BrowsableAttribute)).ToList();
            attributes.Add(new BrowsableAttribute(value));
            typeof(MemberDescriptor).GetProperty("AttributeArray", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(p, attributes.ToArray());
        }
    }
}

WPF 中的 ObservableCollection 不会传播 NotifyPropertyChanged 事件。因此,当 TriggerTypeItems 更改时,不会触发 RefreshProperties 事件。克服此问题的一种方法是通过将 UpdatePropertyGridTriggers 中的 NotifyOfPropertyChange(nameof(propertyName)); 更改为 NotifyPropertyChange("")

来强制发生 NotifyPropertyChanged 事件