在WPF中,我希望有这样一个视图模型,在一个 PropertyGrid
它应该显示 CheckComboBox
使我能够动态地显示其他属性(基于选择)。
我填充了 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>
在WPF中,我想有这样一个视图模型,在一个属性网格中,它应该显示一个CheckComboBox,使我能够动态地显示其他属性(基于选择)。
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事件不会触发。克服这个问题的一个方法是通过改变TriggerTypeItems中的 NotifyOfPropertyChange(nameof(propertyName));
在UpdatePropertyGridTriggers中改为 NotifyPropertyChange("")