PropertyGrid DataObject with subobjects with same property nam

Grids for WPF Forum

The latest build of this product (v25.1.0) was released 2 months ago, which was before this thread was created.
Posted 7 days ago by John Dunn
Version: 23.1.3
Avatar

I have a data model that looks like this

  public class ItemModel<T> : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;

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

    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
      if (EqualityComparer<T>.Default.Equals(field, value)) return false;
      field = value;
      OnPropertyChanged(propertyName);
      return true;
    }
    bool _isOverridden;
    public bool IsOverridden
    {
      get => _isOverridden;
      set => SetField(ref _isOverridden, value);
    }
    public T DefaultValue { get; set; }

    private T _value;
    public T Value
    {
      get => IsOverridden == true ? _value : DefaultValue;
      set
      {
        _value = value;
        IsOverridden = true;
      }
    }
  }
  public class Model
  {
    public ItemModel<string> FirstName { get; } = new ItemModel<string>() { DefaultValue = "Joe" };
    public ItemModel<string> LastName { get; } = new ItemModel<string>() { DefaultValue = "Smith" };
    public ItemModel<int> Age { get; } = new ItemModel<int>() { DefaultValue = 35 };
  }

I tried creating a TypeDescriptorFactory which does expose the child properties - unfortunately they all show up with the same property name - Value

  public class CustomDataFactory : TypeDescriptorFactory
  {
    protected override IList<IPropertyModel> GetPropertyModels(object dataObject, IDataFactoryRequest request)
    {
      if (dataObject is Model model)
      {
        var propertyModels = new List<IPropertyModel>();

        var contentPropertyDescriptors = TypeDescriptor.GetProperties(model.FirstName);
        var propModel = this.CreatePropertyModel(model.FirstName, contentPropertyDescriptors["Value"], request);
        propertyModels.Add(propModel);

        var contentPropertyDescriptors2 = TypeDescriptor.GetProperties(model.LastName);
        var propModel2 = this.CreatePropertyModel(model.LastName, contentPropertyDescriptors["Value"], request);
        propertyModels.Add(propModel2);

        return propertyModels;

      }
      return base.GetPropertyModels(dataObject, request);
    }
  }

Is there a way to change the category and name when using the TypeDescriptorFactory?

Once I get that working my next question will be how do I map my IsOverridden concept in my objects to work like the Columns example ( red icon when Overridden, Reset sets Value = DefaultValue ).

Comments (3)

Posted 7 days ago by John Dunn
Avatar

I'm guessing that deriving from IPropertyModel might be a way to accomplish this but implementing 55 different properties and methods seems a little heavy and I'm not sure what I should be returning for most of those.

Posted 7 days ago by John Dunn
Avatar

This 'appears' to work ( always the best type of working :) ) but for many of the members I'm not sure what should be returned beyond null. 

  public class ResetCommand<T> : ICommand
  {
    private ItemModel<T> _Item;
    public required ItemModel<T> Item 
    { 
      get => _Item;
      set
      {
        _Item = value;
        _Item.PropertyChanged += (s, e) =>
        {
          if( e.PropertyName == nameof(_Item.IsOverridden)) CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        };
      }
    }
    public bool CanExecute(object? parameter)
    {
      return Item.IsOverridden;
    }

    public void Execute(object? parameter)
    {
      Item.Reset();
    }

    public event EventHandler? CanExecuteChanged;
  }

  public class PropModel<T> : IPropertyModel, INotifyPropertyChanged
  {
    private ItemModel<T> _Item;
    public required ItemModel<T> Item
    {
      get => _Item;
      set
      {
        _Item = value;
        _Item.PropertyChanged += (s,e) =>
        {
           OnPropertyChanged(null);
        };
        ResetValueCommand = new ResetCommand<T>() { Item = _Item };

      }
    }
    public void Dispose()
    {
    }

    public bool CanAutoDispose => true;
    public DataModelCollection Children { get; } = new DataModelCollection();
    public string Description { get; set; } = "";
    public string DisplayName { get; set; } = "";
    public bool IsExpanded { get; set; }
    public bool IsInitialized { get; set; }
    public bool IsModified => Item.IsOverridden;
    public bool IsRoot => false;
    public bool IsSelected { get; set; }
    public string Name { get; set; } = "Some name";
    public IDataModel Parent { get; set; }
    public DataModelSortComparer SortComparer { get; set; }
    public DataModelSortImportance SortImportance { get; } = DataModelSortImportance.Property;
    public int SortOrder => 1;
    public object Tag { get; set; }
    public void AddChild()
    {
    }

    public bool CycleToNextStandardValue()
    {
      return false;
    }

    public void Refresh(PropertyRefreshReason reason)
    {
    }

    public void Remove()
    {
    }

    public void ResetValue()
    {
      Item.Value = Item.DefaultValue;
    }

    public ICommand AddChildCommand { get; } = null;
    public bool CanAddChild => false;
    public bool CanRemove => false;
    public bool CanResetValue => true;
    public string Category { get; set; } = "Category Name";
    public TypeConverter Converter { get; }
    public bool HasStandardValues => false;
    public bool IsHostReadOnly { get; set; }
    public bool IsImmutable => true;
    public bool IsLimitedToStandardValues => false;
    public bool IsMergeable => false;
    public bool IsReadOnly => false;
    public bool IsValueReadOnly { get; set; }
    public PropertyEditor NamePropertyEditor { get; set; }
    public DataTemplate NameTemplate => null;
    public object NameTemplateKey => null;
    public DataTemplateSelector NameTemplateSelector => null;
    public ICommand RemoveCommand => null;

    public ICommand ResetValueCommand { get; set; } = null;
    public bool ShouldNotifyParentOnValueChange { get; }
    public IEnumerable StandardValues { get; } = new List<string>();
    public IEnumerable<string> StandardValuesAsStrings { get; } = new List<string>();
    public string StandardValuesDisplayMemberPath { get; set; }
    public string StandardValuesSelectedValuePath { get; set; }
    public object Target => Item;
    public Type TargetType => typeof(T);

    public object Value
    {
      get => Item.Value;
      set => Item.Value = (T)value;
    }

    public string ValueAsString
    {
      get => Item.Value.ToString();
      set { Item.Value = (T)Convert.ChangeType(value, typeof(T)); }
    }
    public PropertyEditor ValuePropertyEditor { get; set; }
    public IList<object> Values { get; } = new List<object>();
    public DataTemplate ValueTemplate => null;
    public object ValueTemplateKey => null;
    public DefaultValueTemplateKind ValueTemplateKind => DefaultValueTemplateKind.None;
    public DataTemplateSelector ValueTemplateSelector => null;
    public Type ValueType => typeof(T);
    public event PropertyChangedEventHandler? PropertyChanged;

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

    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
    {
      if (EqualityComparer<T>.Default.Equals(field, value)) return false;
      field = value;
      OnPropertyChanged(propertyName);
      return true;
    }
  }

  public class CustomDataFactory : TypeDescriptorFactory
  {
    protected override IList<IPropertyModel> GetPropertyModels(object dataObject, IDataFactoryRequest request)
    {
      if (dataObject is Model model)
      {
        var propertyModels = new List<IPropertyModel>();
        propertyModels.Add(new PropModel<string> { Item = model.FirstName, DisplayName = "First Name"});
        propertyModels.Add(new PropModel<string> { Item = model.LastName, DisplayName = "Last Name" });
        propertyModels.Add(new PropModel<int> { Item = model.Age, DisplayName = "Age" });

        return propertyModels;

      }
      return base.GetPropertyModels(dataObject, request);
    }
  }
Posted 4 days ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

Since you are abstracting things out into other layers, the default data factory won't work well here without additional custom logic added.

For instance, you are telling it to use the ItemModel.Value property as the value to edit. While that does work with getting the proper property editor to show, every property's name in the name column will be "Value".

To adjust the name, you'll need to probably do something like what we did with CustomPropertyModel in the PropertyGridDynamicProperties sample where we override some "xxCore" properties. In your case, you'd pass in an instance of your ItemModel to that class and override the DisplayNameCore property to return an appropriate name. You'd override CategoryCore to return the category you want.  By using a slimmed down version of our CustomPropertyModel in the sample, you can save on doing a lot of work of building out IPropertyModel functionality from scratch.

Then you need your data factory to override CreatePropertyModel and return an instance of CustomPropertyModel. Once you do those things, it should be categorized and named appropriately.

The Columns QuickStart shows the ellipse when the IPropertyModel.IsModified property is true. You could point that to another property on your CustomPropertyModel. Calling propertyDescriptor.ResetValue() will reset the value later.

I hope that helps!


Actipro Software Support

Add Comment

Please log in to a validated account to post comments.