SelectedObject and enabling properties

Grids for WPF Forum

Posted 10 years ago by Rick Edwards - UK
Version: 9.1.0500
Avatar
Hi all,

is it possible to disable a property or several properties of a PropertyGrid.SelectedObject based upon the value of another property (a checkbox or a selected item in a drop down for example)? The selected object is determined at runtime by selecting an object in a tree and setting it as a "SelectedObject" property on the main window ViewModel to which the property grid binds to.

So I guess I need to know

a) what property in the collection has changed at runtime?

and

b) how to find and enable other properties in the collection at runtime?

All selected objects implement INotifyPropertyChanged so I have a feedback mechanism for when a property is changed but I can't work out how to tie this back into other properties in the collection, particularly through XAML data binding.

I'm a complete newbie at WPF so am currently finding my feet and don't even know if this is possible though I suspect it's the sort of thing others might have tried. I've read the forum postings here but think I need to step back a little as I'm not sure I understand them. I'm also not very experienced in developing PropertyGrids in general so any advice good!

Regards

Rick

Rick

Comments (10)

Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Rick,

There really isn't a mechanism to disable properties when using SelectedObject(s), but you can make the properties read-only (which is effectively the same thing).

You can do one of several things:

1. Implement ICustomTypeDescriptor on your object. You would then need to dynamically add the ReadOnlyAttribute (initialized to true) to the properties you want "disabled" based on the setting of another property.

If you go this route, then you would also need to manually call PropertyGrid.Refresh for the changes to be picked up.

2. Create a custom DataFactory that returns custom IPropertyDataAccessor instances, that dynamically change their IsReadOnly property based on the setting of another property.

With this route, the changes can be picked up dynamically, but is limited to our implementation of the PropertyGrid. Item #1 above would also work with the WinForms PropertyGrid.

3. Use an implicit Style for the PropertyGridDataAccessorItem, that includes one or more style Triggers that change the IsReadOnly based on certain properties.

This will work, but moves the logic to the UI layer which probably isn't what you want.

Hopefully this will help get you started.


Actipro Software Support

Posted 10 years ago by Rick Edwards - UK
Avatar
Hi,

thanks for the response. I've tried to implement the second option and have created a custom DataFactory that extends the TypeDescriptorFactory and overrides the GetProperties method. Here I have created a CustomDataPropertyAccessor object that implements the interfaces ICustomPropertyDataAccessor and IPropertyDataAccessor and takes an IPropertyDataAccessor as a constructor argument (its a simple wrapper basically). The ICustomPropertyDataAccessor has my IsReadOnly property which has get and set defined.

In the GetProperties method of the DataFactory I simply call the base GetProperties method and then iterate over the collection to create a new list containing my custom data accessor interfaces which I return, much in the same way as the Dictionary demo application. This all works fine but if I change the IsReadOnly value of an item in my custom collection how do I register the changes with the property grid?

Any advice? Don't think the penny has dropped quiet yet!

Regards

Rick

[Modified at 06/12/2009 03:38 AM]
Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Rick,

You would need to implement INotifyPropertyChanged also, then raise the PropertyChanged event for the "IsReadOnly" property on the instance of CustomPropertyDataAccessor whose read-only state is changing.

Since you are wrapping the existing IPropertyDataAccessor, then you would need to hook into the existing IPropertyDataAccessors's PropertyChanged event and forward those events through your wrapper object. Assuming the existing IPropertyDataAccessor implements INotifyPropertyChanged.

You may be better off deriving from DataAccessorBase or PropertyDataAccessorBase, as these implement INotifyPropertyChanged and provide OnPropertyChanged methods you can call.


Actipro Software Support

Posted 10 years ago by Rick Edwards - UK
Avatar
Thanks for the pointer I managed to get this working but have hit a snag with respect to my custom DataFactory and am having real problems trying to work out how to extend it.

Basically my custom data factory creates a private list of custom data accessors and returns it through the GetProperties method. I've added a ParentAccessor check to my custom data accessor list generator to ensure I'm only looking at the current selected object:


private List<IPropertyDataAccessor> accessorList = new List<IPropertyDataAccessor>();


        protected override IList<IPropertyDataAccessor> GetProperties(object value, DataFactoryOptions options)
        {
            IList<IPropertyDataAccessor> baseData = base.GetProperties(value, options);
            if (baseData != null && options.ParentAccessor == null )
            {
                foreach (IPropertyDataAccessor dataAccessor in baseData)
                {
                    this.accessorList.Add(new CustomDataPropertyAccessor(dataAccessor));
                }

                if (value is IViewModel)
                {
                    ((IViewModel)value).UpdateDisplay();
                }
                return this.accessorList;
            }
            else
            {
                return baseData;
            }
        }
This seems to result in a sensible property tree.

I then call an update method on my Data factory via a mediator supplying a list of properties I want to turn on or off:

        [MediatorMessageSink(MediatorMessages.UPDATE_PROPERTYGRID, ParameterType = typeof(PropertyTrigger))]
        public void UpdatePG(PropertyTrigger trigger)
        {
            foreach (CustomDataPropertyAccessor item in this.accessorList)
            {
                if (trigger.SlaveProperties.Contains(item.ValueName))
                {
                    item.IsReadOnly = trigger.IsReadOnly;
                    
                }
            }
        }
This seems to do the trick. However, if a property is itself another complex object, it's properties are added to the property tree, if I turn off (make read only) this property then I'd want to disable all the properties associated with it. How can I go about this? I don't want to disable the expander.

Any ideas gratefully received.

Regards

Rick

[Modified at 06/19/2009 06:47 AM]

[Modified at 06/19/2009 07:35 AM]
Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Rick,

You would need to use a custom IPropertyDataAccessor that would perform a Boolean AND on it's IsReadOnly and the Parent.IsReadOnly. This could just be a wrapper class around the real IPropertyDataAccessor and passes all calls directly to it (except for IsReadOnly obviously).

You would also need to detect when the parent's IsReadOnly property changed and raise the PropertyChanged event for IsReadOnly. Assuming you are using our IPropertyDataAccessors that are derived from PropertyDataAccessorBase, then you can simply cast the object to PropertyDataAccessorBase (which provides easy access to hooking into the event and raising the event).

Hope this helps.


Actipro Software Support

Posted 10 years ago by Rick Edwards - UK
Avatar
Hi thanks for your help so far but I'm still having some issues with programatically selecting and disabling properties.

Basically I've created a custom DataFactory, overriden the GetProperties method to return a list of my CustomDataPropertyAccessor objects as an IList<IPropertyDataAccessor>. In order to disable properties I create a private list of my CustomDataPropertyAccessor objects in the DataFactory which allows me to set the IsReadOnly property based upon some trigger (see code in earlier post). The problem I now have in in creating my list when properties themselves are complex objects.

If I only add items when options.ParentAccessor == null then I don't get the properties of my child complex object, if I only add items when options.ParentAccessor != null then clearly I don't get my selected object properties and if I ignore options.ParentAccessor altogether then I have to clear my private CustomDataPropertyAccessor list each time and so I lose my ability to turn properties on and off.

So I have a couple of questions.

Firstly is it possible to access the DataPropertyAccessor list the control currently has and retrieve individual PropertyAccessors? Ideally I'd like to be able to do this in the DataFactory or at least not in the xaml.cs "code behind". I need to be able to get to my CustomDataPropertyAccessor to switch IsReadOnly given ca. the property name somehow.

And secondly how do I detect when a parents IsReadOnly property has changed?

Thanks again, can't help feeling I'm missing something obvious here....

Regards

Rick
Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Rick,

The DataFactory is going to be called as needed to retrieve the needed accessors, both for the SelectedObject(s) and the sub-properties. Trying to save the accessors in the DataFactory across calls can get messy, since the DataFactory isn't really intended to have a "state".

That said, you can probably still do it that way, but you would need to clear the "state" when you receive a call to GetDataAccessors(object[], DataFactoryOptions) (where the options have a ParentDataAccessor == null).

The PropertyGrid is basically just a fancy TreeView. It's Items will contain the accessors, and it uses a PropertyGridDataAccessorItem as a container (this is equivalent to the TreeViewItem). The PropertyGridDataAcessorItem's Items will then contain the sub-properties or categories. The DataFactory is simply used to convert from the PropertyGrid.SelectedObject(s) to a list of items to assign to the Items property (via the ItemsSource).

It sounds like you should have a "data source", which would be your accessors. These accessors would be tied together (via your implementation) so setting IsReadOnly on the parent accessor would change the associated property on the child accessors. To do this, you would either need to pass a reference to the parent down to the children (so they can hook into the parent's change event) or have the parent manually "refresh" the children when it's IsReadOnly is changed. You could then build in methods/properties for you to gain access to the accessors.

The DataFactory would then serve up the accessors from the data source upon request. So instead of creating new accessors for every call.

It might be best if you can send over a small sample project to our support address so we can take a look.


Actipro Software Support

Posted 10 years ago by Rick Edwards - UK
Avatar
Ok I have sent some example code to the support address for you to get a better idea as to what I'm trying to achieve.

Thanks for your help, this is unfortunately a key requirement for us so I have to find a suitable workaround as its a potential show stopper.

Rick
Posted 10 years ago by Rick Edwards - UK
Avatar
For completeness I thought I'd post the follow up to this I received from Support, it's been snipped from the reply they sent, all very useful for customisation:

1. As for turning off the sub-properties (e.g. properties under another property), you would need to have your CustomDataPropertyAccessor listen to it's parent's PropertyChanged event. If you change the constructor of CustomDataPropertyAccessor to:

public CustomDataPropertyAccessor(IPropertyDataAccessor dataAccessor) {
this.dataAccessor = dataAccessor;

INotifyPropertyChanged propertyChanged = dataAccessor.Parent as INotifyPropertyChanged;
if (null != propertyChanged)
propertyChanged.PropertyChanged += new PropertyChangedEventHandler(propertyChanged_PropertyChanged);

// ... existing code
}

Then the child property can listen for IsReadOnly changes, with an event handler like:

private void propertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "IsReadOnly")
this.OnPropertyChanged("IsReadOnly");
}

This means that when the child will raise a changed event for the IsReadOnly property whenever the parent IPropertyDataAccessor does. Finally, you need to have the IsReadOnly property take the parent's read-only setting into account like so:

public bool IsReadOnly {
get {
return this.isReadOnly || (null != this.dataAccessor.Parent && this.dataAccessor.Parent.IsReadOnly);
}
// .. existing code
}

Works very well so thanks for that.

Regards

Rick
Posted 2 years ago by rex hui
Avatar

Never mind.

[Modified 2 years ago]

The latest build of this product (v2019.1 build 0683) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.