|
Hello.
I believe that your scenario is pretty close to something that I had faced some years back wherein depending on certain values of some properties, I had to display/hide some other properties of the same instance. It was a part of a bigger problem, but nonetheless was similar to your situation. The difference is that you wish to make them read-only while I had to just stop showing them.
To come back to your scenario, here is what comes to my mind. You can have a list of objects (called the _exceptionalObjects in the below example; with a public property of course) in RPropertyGrid which determines whether or not they should be wrapped using the ReadOnlyWrapper . After this, you can put a check in the SetReadOnly method to see if the current object being wrapped needs to be handled in a special way. Something like this...
object[] wrappedSelectedObjects = new object[SelectedObjects.Length];
for (int i = 0; i < wrappedSelectedObjects.Length; i++)
{
if(!_exceptionalObjects.Contains(SelectedObjects[i]))
{
wrappedSelectedObjects[i] = new ReadOnlyWrapper(SelectedObjects[i]);
}
}
So depending on which objects you want to be left out of the wrapping (or vice versa) you can add and remove them from this list. This is just something that came to me right away so it may not address your query completely or accurately. If that is the case, I will be more than happy to know the scenario in detail and we could possibly come out with a solution. Otherwise, if this works as per your requirement, I would be happy to know that too.
Thanks !
|
|
|
|
|
Really dislike your responses to a viable alternative. They are defensive and rude.
|
|
|
|
|
Well all I can say is that the 'viable alternative' you mentioned is not really an alternative. I don't think it works (and I don't wish to start an argument again), but at the same time am completely open to accepting it if it does. I just need a proof which was not successfully presented. I would be thankful if you checked the technicalities before jumping to such a conclusion.
I am sorry to know that making direct, albeit polite, statements is considered to be defensive and rude while a bigoted and blatant attack on my understanding is not.
Also, I would really appreciate if you rated the article and not the writer or his responses.
Thank you !
|
|
|
|
|
RPropertyGrid's SelectedObjectsChanged event is triggered twice。
I suggest that change the OnSelectedObjectsChanged method code from
base.OnSelectedObjectsChanged(e);
if (!_selectionChangedInternally)
{
_originalSelectedObjects = new ReadOnlyCollection<object>(SelectedObjects);
SetReadOnly();
}
to :
if (!_selectionChangedInternally)
{
_originalSelectedObjects = new ReadOnlyCollection<object>(SelectedObjects);
SetReadOnly();
}
else {
base.OnSelectedObjectsChanged(e);
}
to resolve this problem .
|
|
|
|
|
You are right. It is being fired twice. Thank you very much for bringing this to my notice.
However, it seems like putting the call to base.OnSelectedObjectsChanged in the else part creates one additional problem - it stops firing the SelectedObjectsChanged event when the ReadOnly property of the grid is set to false and the selected object are actually changed. But putting the call in the if itself works just fine.
if (!_selectionChangedInternally)
{
base.OnSelectedObjectsChanged(e);
_originalSelectedObjects = new ReadOnlyCollection<object>(SelectedObjects);
SetReadOnly();
}
I have checked this for different scenarios and looks like it solves the problem; so I have updated the source code and the binaries with this fix. Please do let me know if you find that the issue persists or some other issues crop up.
Once again, thank you very much for pointing out the fault.
|
|
|
|
|
base.OnSelectedObjectsChanged(e); should placed after
SetReadOnly();
|
|
|
|
|
You can do that but I prefer it the other way for the following reason. When the SelectedObjectsChanged event is fired (when base.OnSelectedObjectsChanged(e) is before the SetReadOnly() ) at least in the event handlers of this event using the SelectedObject(s) property would give them the actual object instead of the instance of the wrapper which would be the case when the sequence of calls in reversed. I kept it this way in accordance with my philosophy that anyone wanting to use my code need not make any significant changes in his existing work.
But you are right, and this could also be a possibility and I think both the approaches are equally correct depending on how the programmer looks at the situation.
|
|
|
|
|
|
|
You don't have to wrap every object assigned to a PropertyGrid's SelectedObjects property, or have to use a custom PropertyGrid class to do this.
It's actually much simpler to implement a TypeDescriptionProvider that targets System.Object (e.g., it will be used for all objects), and add it using TypeDescriptor.AddProvider().
Once you do that, the system will call it's GetTypeDescriptor() method for every object, and you can return your replacement/wrapper ICustomTypeDescriptor that way, and it will be used in lieu of the default ICustomTypeDescriptor.
|
|
|
|
|
Thank you for your message.
I beg to differ from your suggestion for the following reasons:
1) By what you are saying the AddProvider method will end up providing the wrapper as the TypeDescriptor for each and every object. But honestly, that is not what is intended.
2) If you do so, any other control (like DataGridView) or code that wishes to work on the properties of any object will not be able to do so, because all of them will appear as read only.
3) The biggest reason I designed it this way is because I believe that to achieve the read only effect, one should not be forced to change his/her class design, and there should be no interference with the existing structure (even if done by clandestine operations). What you say defies this.
4) The process of making the SelectedObject read only should just be a temporary thing and should not be something as drastic as modifying the objects' metadata.
5) And even if all the above reasons were not true, doing something which would work on a single object is far better than affecting millions of objects unnecessarily. This would be a huge overhead.
However, in case I have misinterpreted your suggestion, please do correct me. And if that is the case, a code sample to explain what your are saying would be extremely helpful.
Thank You.
|
|
|
|
|
" By what you are saying the AddProvider method will end up providing the wrapper as the TypeDescriptor for each and every object"
Not necessarily.
A TypeDescriptionProvider is free to return whatever it wants
on a per-instance basis. It can return a CustomTypeDescriptor
that implements the read-only behavior, or can deferr to the
parent TypeDescriptionProvider's result.
I've used that approach in the past, usually because when I
want an object to be 'read-only' I want it to be read-only
regardless of the UI or control, so it works quite nicely
for that purpose.
"2) If you do so, any other control (like DataGridView) or code that wishes to work on the properties of any object will not be able to do so, because all of them will appear as read only."
Again, that's not really the case. A TypeDescriptionProvider can
act differently for each instance for which an ICustomTypeDescriptor
is requested, so it can make some objects read-only, and others not
read-only.
"3) The biggest reason I designed it this way is because I believe that to achieve the read only effect, one should not be forced to change his/her class design, and there should be no interference with the existing structure (even if done by clandestine operations). What you say defies this."
I don't see how what I say defies that. It involves no change
to the class design, or to the class. The read-only behavior
can be implemented in an ICustomTypeDescriptor (you only have
to override GetAttributes(), and include ReadOnlyAttribute.Yes
in the result).
"4) The process of making the SelectedObject read only should just be a temporary thing and should not be something as drastic as modifying the objects' metadata."
Yes, it is temporary, because an ICustomTypeDescriptor is temporary.
"5) And even if all the above reasons were not true, doing something which would work on a single object is far better than affecting millions of objects unnecessarily. This would be a huge overhead."
Again, you are operating on the assumption that what I suggest is an 'all or nothing' proposition, which is simply not correct.
The TypeDescriptionProvider's GetTypeDesciptor() method can
return a different result for each and every object.
|
|
|
|
|
Thank you for your detailed reply. However I still don't agree with that approach for the following reasons
a. A TypeDescriptionProvider is free to return whatever it wants on a per-instance basis. It can return a CustomTypeDescriptor that implements the read-only behavior, or can deferr to the parent TypeDescriptionProvider's result.
How would your TypeDescriptionProvider class know which objects need to be read only and which not? It is possible to do this only if the class knows all the possible objects that would be displayed in the PropertyGrid and it is almost impossible to get this information. I don't see how you can make this distinction and where exactly would such a class reside. And if we assume that such information were available, I don't need to mention that your method where the distinction happens would need to be modified every time a new object needs to be a part of the possible selectable objects.
b. I've used that approach in the past, usually because when I want an object to be 'read-only' I want it to be read-only regardless of the UI or control, so it works quite nicely for that purpose.
It might have worked in that case, but this is far less likely than the case where you wish that the object retains its original behavior and the controls do what they are supposed to do. The approach of changing the object's behavior universally, even if it is for the time when the read only effect is desired in some controls, is definitely not a very good design.
c. Again, that's not really the case. A TypeDescriptionProvider can act differently for each instance for which an ICustomTypeDescriptor is requested, so it can make some objects read-only, and thers not read-only.
Same as point a
d. I don't see how what I say defies that. It involves no change to the class design, or to the class. The read-only behavior can be implemented in an ICustomTypeDescriptor (you only have to override GetAttributes(), and include ReadOnlyAttribute.Yes in the result).
What I meant here is that to achieve the read only effect, following your way, the user will have to do the dirty work of making new classes dealing with type conversion, type description, wrapping, etc. I believe that the control should take away and abstract all of this thereby facilitating its use by any existing code without any significant modification/addition. Also, the side effect caused by point b, in my opinion, cannot really be taken out of "no interference to the existing structure".
e. Again, you are operating on the assumption that what I suggest is an 'all or nothing' proposition, which is simply not correct.
Same as point a
|
|
|
|
|
"How would your TypeDescriptionProvider class know which objects need to be read only and which not".
The TypeDescriptionProvider class doesn't need to know that.
You just call TypeDescriptor.AddProvider( object instance, ... ), and pass it the specific instance that you want to be read-only. The TypeDescriptionProvider you also pass to that method will only be used for the instance that you pass to AddProvider(), rather than all instances of the type.
When you no longer want the instance to be read-only, you only need to call TypeDescriptor.RemoveProvider(), and pass it the instance that is to no longer be read-only.
The CustomTypeDescriptor that is returned by the TypeDescriptionProvider only needs to override GetProperties() and add ReadOnlyAttribute.Yes to each PropertyDescriptor returned by the parent ICustomTypeDescriptor's GetProperties() method, and that's it.
The advantage of that approach is that it can be used universally with any control that supports data binding, rather than just with a PropertyGrid.
-- Modified Saturday, July 3, 2010 10:49 PM
|
|
|
|
|
The TypeDescriptionProvider class doesn't need to know that.
You just call TypeDescriptor.AddProvider( object instance, ... ), and pass it the specific instance that you want to be read-only. The TypeDescriptionProvider you also pass to that method will only be used for the instance that you pass to AddProvider(), rather than all instances of the type.
When you no longer want the instance to be read-only, you only need to call TypeDescriptor.RemoveProvider(), and pass it the instance that is to no longer be read-only.
So now you are talking something different from using a custom type description for type System.Object and performing the checks (as per your previous replies) in the TypeDescriptionProvider class. Alright.
As for the new approach the question still remains - How would you know what objects are going to be displayed in the property grid. By what you are saying only the root object will have the ability to use the custom type descriptor. All your child properties will not be affected unless you use the AddProvider method for all of them too. And, knowing which objects will form a part of the child properties of your root object is impossible unless your system is very very small and has everything predefined. Also, even if you do know what the objects that form the child property values are, you will have to enumerate them in a method with a AddProvider call, and separately with RemoveProvider call, for all of them. I don't need to point out that this is a very sloppy design and uses code that needs to be modified for even the slightest change in the system since it is not universal and is completely dependent on whether or not the given object has an AddProvider call associated with it. Also, if you ever ship such a design and later realize that some more objects could end up being shown in the property grid you will have no option but to create a new assembly to accommodate this addition and replace the old assembly; and this will have to be done every time a new object needs to be displayed in the grid.
This would get worse if you have a plug-in based architecture where you have no idea about the objects that may be displayed; here your approach simply fails.
The advantage of that approach is that it can be used universally with any control that supports data binding, rather than just with a PropertyGrid.
The advantage is not limited to your approach. The ReadOnlyWrapper class (that I have deliberately made private in this article) can very well be used in all data binding scenarios. And again, as in this case, there would be no need whatsoever for any change in the client side of the code and there would be no side effect that I had mentioned in my previous reply.
I honestly believe that you should actually implement your design and see its disadvantages (and non applicability in many situations) yourself. And, if after implementing you feel that I am wrong, I will be more than happy to see your code and accept it, if it does overcome all that I have mentioned above.
|
|
|
|
|
"So now you are talking something different from using a custom type description for type System.Object and performing the checks (as per your previous replies) in the TypeDescriptionProvider class"
I've done it both ways depending on the specific requirements, and both of them work equally well. When there is no PropertyGrid involved (and hence, no expandable objects), the AddProvider() method works and is really nothing more than a different way of doing what your code does (put an ICustomTypeDescriptor wrapper around an object).
If you want properties of expandable objects to also be read-only, the customized ICustomTypeDescriptor can call AddProvider() for each object for which it's parent ICustomTypeDescriptor's GetPropertiesSupported() method returns true, and that takes care of that. Of course, that requires a globally-visible list of all objects for which AddProvider() was called, so that RemoveProvider() can be called on each.
"I honestly believe that you should actually implement your design and see its disadvantages"
Where did you get the impression that I've never done this??? My own work with System.ComponentModel has involved much more than merely making objects 'read-only', and yes, it has included that, but also making those objects read-only in all UI's rather than just a PropertyGrid.
The work I've done with System.ComponentModel has included making most or all objects expandable in the PropertyGrid (where they would otherwise not be); adding additional 'dynamic' properties to classes (e.g. properties that are not declared on a type), and providing values for them; dynamically providing custom TypeConverters and UITypeEditors for more-specialized formatting and editing of various types that I do not own; and various other things like, for example, adding support for AutoCompletion to the PropertyGrid, on both a per-property and per-type basis.
If I can ever find the time to clean up some of that code and remove dependencies on things I can't make public, I'd love to make some of it available on Code Project, but unfortunately, my time right now is very limited because of my workload.
So actually, the 'read-only thing' is relatively simple and straightforward compared to some of the other stuff I've had to do with System.ComponentModel to achieve a broad variety of design objectives in my work.
I would appreciate you're not being too presumptious, If you don't mind.
|
|
|
|
|
OK. Looks like you did not like the 'lets solve this using code' approach.
"I've done it both ways depending on the specific requirements, and both of them work equally well. When there is no PropertyGrid involved (and hence, no expandable objects), the AddProvider() method works and is really nothing more than a different way of doing what your code does (put an ICustomTypeDescriptor wrapper around an object)."
If that is the case I don't understand why you called the title of your thread 'Another Way', which in this context should mean a different way of doing what this article is about.
"If you want properties of expandable objects to also be read-only, the customized ICustomTypeDescriptor can call AddProvider() for each object for which it's parent ICustomTypeDescriptor's GetPropertiesSupported() method returns true, and that takes care of that. Of course, that requires a globally-visible list of all objects for which AddProvider() was called, so that RemoveProvider() can be called on each."
So now a new design - adding AddProvider calls in custom type descriptor. Alright. Whatever said and done, if you need to maintain a list of objects to achieve what you are saying I simply don't get how does it even compare to the absolutely universal method I have provided.
"Where did you get the impression that I've never done this??? My own work with System.ComponentModel has involved much more than merely making objects 'read-only', and yes, it has included that, but also making those objects read-only in all UI's rather than just a PropertyGrid."
I got this impression from your change of approach in each reply and refusal to accept that the kind of design you are talking about does not work in real life. Also, please stop assuming that just because the control I have used in this article is a PropertyGrid this design works only for it. And, as you say, if you really have the experience of working with ComponentModel I don't think I need to say this to you specifically.
"The work I've done with System.ComponentModel has included making most or all objects expandable in the PropertyGrid (where they would otherwise not be); adding additional 'dynamic' properties to classes (e.g. properties that are not declared on a type), and providing values for them; dynamically providing custom TypeConverters and UITypeEditors for more-specialized formatting and editing of various types that I do not own; and various other things like, for example, adding support for AutoCompletion to the PropertyGrid, on both a per-property and per-type basis."
If your intention of writing the details of what you have worked on was to scare or impress me, then just to set the record straight - I have worked with all that you have mentioned (and much more) for more than 5 years. So, although I don't wish to boast, the things you have mentioned was a pretty small part of my actual work. However, that does not stop me from accepting something if it really is correct.
"If I can ever find the time to clean up some of that code and remove dependencies on things I can't make public, I'd love to make some of it available on Code Project, but unfortunately, my time right now is very limited because of my workload."
Its a matter of not more than 15 minutes to code what you propose. You don't need to find time out of your 'busy workload' to implement it.
"So actually, the 'read-only thing' is relatively simple and straightforward compared to some of the other stuff I've had to do with System.ComponentModel to achieve a broad variety of design objectives in my work."
I would appreciate if you said this was simple only after demonstrating that you have a way of doing this which is as universal as my approach. And again, if you are saying that the things you have mentioned like making properties expandable (for heaven's sake it is as simple as using the ExpandableObjectConverter statically or dynamically), adding dynamic properties, dynamic TypeConverters, or making custom UITypeEditors are less straightforward and more difficult, then I really don't think there is any point arguing further.
"I would appreciate you're not being too presumptious, If you don't mind."
I was not being presumptuous; I was simply making it easier for both of us. The only right way of settling this is to see a code that works. As I mentioned in my previous reply, I have no issues in accepting your approach provided it works. And if it does, I'll be more than happy to have learnt an alternative way.
Honestly, now I am too tired to take this discussion any further. By changing your approach in each reply, getting infuriated by my replies, and assuming that by enumerating simple tasks involving System.ComponentModel you could make me believe that you know a lot about all this, you are simply questioning my intelligence. And I don't really appreciate that.
Thank you very much !
|
|
|
|
|
"If that is the case I don't understand why you called the title of your thread 'Another Way', which in this context should mean a different way of doing what this article is about."
You're right. It should have been "Several other ways",
rather than 'another way', and of course, which way is
best depends on precisely what you need to do. While
your specific requirements may have been addressed by
your code, not everyone's specific requirements are the
same as yours and I suspect that most of them would be
more similar to the 'read-only-in-ALL-UIs', rather than
only in a single PropertyGrid, so I don't understand what
the knee-jerk reaction to it is all about.
I'm not interested in being confrontational about this,
it was both a relatively different way of doing what
your code does to address requirements you code does
not address, and another way of putting a wrapper that
implements ICustomTypeDescriptor around an object,
which is basically what a TypeDescriptionProvider does.
I'm outta here.
|
|
|
|
|