I will use the same code base, upgraded with new features. I will also refer to this work where it is needed for proper understanding of the matter. Conceptually, the present work is derived from the code of the article #2, but the code of the Enumerations library itself is derived from the code of the article #3, where the additional item as introduces, abbreviations of the names of the enumeration members. So, I gave this library a version number of 4.0. The new library is Enumerations.UI v.1.0.

1.1. Beyond the Article Title

The class EnumerationEditor is not really redundant. Not only it complements the class BitwiseEnumerationEditorin a matching styles; both classes show human-readable names and descriptions of the enumeration members, shown in the node tooltips on the tree nodes enumeration member values.

1.2. The Demo Application

Please look at the picture shown on top of this article. It demonstrates how the enumeration values can be edited in Visual Studio for a component. To see the demo in Visual Studio, build the solution, then locate the main form node of the Solution Explorer, click it to show the designer, select the bright rectangular control on left (it does nothing, just carries two demo enumeration properties) and activate the Properties window, locate the category "Demo" shown on the picture by the raspberry-colored arrow. It will demonstrate two editors; the property "Feature Set" uses the bitwise enumeration editors.

To see the same thing during runtime, just run the application. Even though the demo application is written in System.Windows.Forms, the library provided can be used in assemblies of all other types: WPF, ASP.NET, Silverlight or any other type, because, anyway, the enumeration types will be shown and edited in the same way, in Visual Studio and other, alternative .NET IDE.

2. The Usage by Example

Now, let's review the code sample used to obtain the PropertyGrid behavior described above:

I provided separate aliasusing directives for all of the attributes used, to show where each one comes from. The usage of the attributes DisplayName and Description is described in detail in my second article of this series; the use of abbreviations — in the third one. The Editor attributes define what editor should be used in PropertyGrid. The values defined by the Description attribute are shown in the tooltips of the tree nodes representing the enumeration member values.

Note the use of the NonEnumerable attribute. When dealing with the bitwise editor, it's very important to avoid showing the enumeration members not representing single bits.

Note that the Flags attribute is used. Generally, this attribute does not affect application runtime, but it is important to use the Flags attribute if System.Object.ToString() is used for an enumeration type; it may affect design time, useful for debugging, and so on…

3 How it Works?

3.1 The Main Idea

The whole idea of the technique used to edit the enumeration values in an agnostic manner, as required by the System.ComponentModel.EditorAttrubute and the universal editor which should be automatically invoked from any instance of the ProperyGrid, is just one subliminal method: System.Type.MakeGenericType.

Even the name of this important method is misleading: it actually expects a generic type to be an instance of Type on which this method is called, and substitutes the generic parameters by actual types and instantiates a generic type into a new type, which can make the returned type a fully-defined type, that is, non-generic. This is what happens in my code: the call creates a fully-defined type, typically an enumeration type.

Why doing so? Of course, it would be fairly easy to develop the editor and all required enumeration utilities base on reflection from scratch. But I wanted to reuse the mechanism I introduced in my first article of this series. That work introduced enumeration through enumeration type members and other important features which are much more fundamental than the topic of the present article; and the code is based on the generics, which is very important for the usage of concrete enumeration types. Programming with enumeration types in a type-agnostic manner is quite tricky, which is well illustrated by the next section about the "most difficult problem".

So, the idea was to use reflection code developed for concrete enumeration types, which performs reflection on the generic types. For this purpose, I've segregated the abstract class EnumerationItemBase from the derived generic class Enumeration<ENUM>, both representing a single enumeration member with all its properties. The only addition to the base class is the generic-type property and field representing the concrete-type enumeration member value, and the reflection mechanism itself. Naturally, the type-agnostic reflection should work with the runtime type of the object passed from the editor (with the compile-time typeSystem.Object, of course) and return the objects of the abstract base type EnumerationItemBase. This is how:

This code is quite sophisticated but yet very simple. The core implementation using reflection is implemented in the constructor of the generic class Enumeration<enum>. It is explained in detail in my first article of this series.

This will also work, but here is what happens next: in both enumeration editors, I check up in the type of the edited value is enumeration type or not. Apparently, there is no any sense to edit not an enumeration type, so the editor is simply not invoked. As it's not possible to check up during compile-time if some type-level attribute is applied to a right type, this is a reasonable solution which helps the developers to see the problem as soon as possible.

By the way, all the key or tricky techniques of this whole work are put in one file, "EnumerationUtility.cs". We just discussed one key technique; now let's discuss the most difficult issue I faced.

3.2 The Most Difficult Problem: the Underlying Integer Types

The real problem is the one specific to enumeration types, nothing else: they have different underlying integer types. And the mechanism of generics cannot be applied to primitive types. But working with separate bits requires using numeric integer types. Yes, this is also possible with enumeration types (as shown in my first article of this series), but… it requires concrete enumeration type, cannot be done directly for the abstract type System.Enum.

This would not be a problem if all of those types were unsigned (or even signed). In this case, all enumeration values could be converted to the "widest" type, for example, System.UInt64. No, this is impossible: there is no the "widest" type in the set of all signed and unsigned type. This is can be easily seen on the two "widest" candidates: one among the signed, another — among the unsigned types:

Apparently, the domains of the values intercept. If, by some weird reason, the user of the library uses ulong base type and actually use high explicitly defined values for some member, the conversion to long, or any other signed type will throw the System.InvalidCast exception. If the user uses long and actually uses any negative member value, the attempted conversion to ulong will throw the same exception. What do to? I recognized several possibilities:

The use of the class System.Runtime.InteropServices.Marshal to serialize any unknown type to array of bytes. The problem of this and next solution is that interoperation services always impose some risk of compromising of the platform compatibility of the code. Besides, the code is complex and next to impossible to debug, error-prone.

Similar to the above, using Marshal to create something analogous to C++ reinterpret_cast.

The use of the unsafe mode of the assembly would allow working with pointers to the instance of the enumeration value. Using unsafe without special need for it is, well… unsafe. Such code is simper but also can be error-prone and the problems are hard to spot; memory pinning is involved.

Values can be serialized into a memory stream and then parsed from a stream. Well, in addition to apparent performance hit (who cares about performance when it all happens on a separate click in the UI?), this solution is highly artificial. I call it "scratching right ear with left leg".

So, having it all in mind, I came to probably the simplest solution: using both long and ulong types with possible handling of the exception mentioned above:

Apparently, the use of non-negative enumeration values is, by far, the most likely; so the code starts from the attempt of using ulong. In case of any negative value, ulong conversion is failed. Such cases are covered by using the type long.

I think this solution is the simplest and quite reliable. I will be highly excited if someone finds something better. I will be very impressed if someone finds a flaw, and will do my best to fix it.

In addition to the above is setting the initial value of the abstract-type enumeration value to some "generic" binary zero, which is, apparently, required to update the value from the bitwise editor. It also took some thinking but is much easier:

Let's see how long this code can survive. This functionality can actually be eventually broken. To break it, 1) new CLR standard should introduce 128-bit integer types (or other types wider then long and ulong); 2) it should allow these types as the underlying integer type of enumeration type; 3) some user should actually use such types and use either negative or "big" values, to get in the part of the domain beyond the domain of long and ulong. If that happen, the fix will be needed to the code shown above: long and ulong should be replaced with those new type.

Even though 128-bit architectures are actually emerging, something is telling me that the fix won't be needed any time soon.

The only less-than-obvious issue is to obtain the instance of the type shown the drop-down; it is of the type IWindowsFormsEditorService.

The rest of the code uses the enumeration utility described above and the facilities explained in my first and second articles of the series.

4. Conclusions

The present work rounds up my series on enumerations pretty well. The most recent version of all of the code covered by all the articles of the series can be found in the source code of the present article. The previous article adds library of the more application character: command line utility.

However, I've already made one little mistake in my previous article on the topic: I called it the last one in the series. At this moment, I also feel that the present article is the last one, but this time I am not so sure. If, by any chance, I got another idea on the topic, I'll try to write about it. If some readers give ask me an interesting question or give me another idea on the topic in this or any other way, I'll be very excited about it and will try to work at it. Which brings us to the last section…

5 Credits

I want to mention two CodeProject members in connection to this work.

Grant Frisken is the author of this CodeProject article: Localizing .NET Enums. He argued with me on the features of my library described in my first work. He insisted on the importance of the use of the type converters and System.ComponentModel.TypeConverterAttribute. I always criticized System.ComponentModel for pure maintainability and general architectural flaws and insisted that my approach has more fundamental and clear character. I hope we finally understood each other in this very interesting and friendly discussion. To finally reconcile our opinions, I can now add that this first work was correct, and it wasn't the place for the type converter, because it was unrelated to any UI. However, providing the "editability" for an enumeration type through PropertyGrid really requires the use of the type converters. It's just the right moment for doing so has come. :-)

Even though Grant's information did not give me the idea of the present work (which stems from my own works unrelated to the topic and my enumeration-related works referenced at the beginning of the article), I want to credit his interesting and useful article, and himself, for deep understanding of the matter and being a knowledgeable, helpful, open-minded and collaborative developer and a community member.

As to the idea of the present work… Bigbro_1985 asked me several interesting questions which just inspired me to write this article. Also, he brought the idea of using a tooltip to my attention; and I used the tree node tooltips for showing the description texts of the enumeration members. Thank you, Marco!

[UPDATE]

On April 4, 2017, BillWoodruff reported a critical problem I've fixed on the same day.

First, congratulations on just getting (at design-time, in the ProprtyGrid) a drop-down showing the various bit-flag enum values in 'FeatureSet as CheckBoxes !

fyi: this 2006 article ere by LogicNP: [^] ... does achieve design-time editing of Enums, and both 'all and 'none CheckBoxes work as expected.

Bug: At design-time, the 'All CheckBox stays always checked no matter which CheckBoxes you check, or uncheck.

If I remove the [NonEnumerable] Attribute from your 'FeatureSet enum values: then design-time select a mix of values: you get a design-time error, and the enum value of the Featureset variable will be set 'None : example:

I like forward to seeing your code handling arbitrary bit-field selections at design-time, and supporting 'All and 'None selections !

cheers, Bill

«When I consider my brief span of life, swallowed up in an eternity before and after, the little space I fill, and even can see, engulfed in the infinite immensity of spaces of which I am ignorant, and which know me not, I am frightened, and am astonished at being here rather than there; for there is no reason why here rather than there, now rather than then.» Blaise Pascal

Thank you so much for your bug report. Or, that design-time… Now, could you clarify a bit, what exactly is that design time and how you get to it. First, you can simply have such property in your code and edit its value in Visual Studio, as it's shown on the article picture. Or you can add some kind of PropertyGrid in your application and do something with this PropertyGrid at design time. Or something like that…

[UPDATE] I can change the bits to any combination on all modes, .NET v.3.5 to 4.6.1, just tested, but… Do you mean the steps described in my article, properties of the "testConrol" object, bright rectangle on right? If so, perhaps you mean the different thing: "code generation for property… failed" and "The value… is not the valid value for the enum…"? Then I understand the problem, will try to resolve it.

I've put a quick fix to the new source I've just uploaded. Why "quick"? I've found that the problem is not in the editor itself, but the type converter. The editor always worked correctly, in design time and runtime, but my type converted attempted to introduce "correct" '|' instead of really ugly ','. I simply gave up '|'. The code generation from the "designer mode" is a mystery, never documented and, as it turned out, not directly or fully controlled by

The solution is: first, on the application side, in all cases, if the enumeration type is supposed to be bitwise or combined, System.FlagAttribute should be applied:

[Flags]
enum MyEnum {...}

Of course, it does not affect the runtime of the application being developed, but affects design time, useful during debugging, and so on.

And them, I removed that type converter from the project — now you will see the default ',' as a enumeration member separator (how ugly!). I'll take it into account in my other projects.

Yes, I see you have marked the 'All,'None, and 'Center Enum Values as [NonEnumerable], and, yes, the design-time editing of the instance of the Enum on 'FormMain works as expected, and you can successfully use combinations; that is good !

It would be "icing on the cake" if you could allow the 'All, 'None, and 'Center values to both appear in the PropertyGrid DropDown, and function in design-time editing of the Enum.

The 2006 article I cited, [^], has solved this problem. I've modified that code so I get immediate design-time UI updating based on design-time Enum editing in the PropertyGrid. I'll include that when I publish the custom control I am working on (with, of course, full credit for the custom TypeEditor author).

That said, I have never "gone deep" into the whole business of custom design-time Type Editors, and remain in awe of those who do

For those who are intelligent and curious, Life without challenges would be intolerable

cheers, Bill

p.s. There's a lot of things in your code-bundle including references to TreeView/TreeNode ... what's that about ?

«When I consider my brief span of life, swallowed up in an eternity before and after, the little space I fill, and even can see, engulfed in the infinite immensity of spaces of which I am ignorant, and which know me not, I am frightened, and am astonished at being here rather than there; for there is no reason why here rather than there, now rather than then.» Blaise Pascal

At that point, perhaps I have to review the whole design, but I need a break from other works to take more time. I'm not yet sure that items line "None" or "All" should even be mixed in a UI list with one-bit enumeration member. I would think it will bring more of confusion than convenience. "None" and "All" are not bits; they can be the user-defined enumeration members conveniently grouping some subsets of bits. They can be group in other ways, such as "horizontal bits" vs "vertical bits" groups. Instead, an editor could simply have a special universal controls saying "set all bits" and "clear all bits" (except those marked as [NonEnumerable]). Again, there is something to think about, to look at the article you referenced (thank you for that).

As to your or mine attitude to editor development… Editor and development for design-time are different thing. The editor and Forms runtime use of PropertyGrid is a poor necessity. As to the design-time: I wish design-time for Forms never existed, so low is my opinion on its design. I recommend everyone to greatly improve development performance by keeping away from Form designer except the very skeleton items. I generally highly dislike the whole .NET ComponentModel, which is one of the weakest links in .NET, something close to the magic word style of development. So, to me, it's no more than some minimal obligation to the community: if you say A, say also B.

C:\Users\....\BitwiseEnumerationEditor\EditorDemo\EnumerationEditorDemo.csproj : error : The project file could not be loaded. Could not find a part of the path 'C:\Users\....\BitwiseEnumerationEditor\EditorDemo\EnumerationEditorDemo.csproj'. C:\Users\Uruk\Desktop\BitwiseEnumerationEditor\EditorDemo\EnumerationEditorDemo.csproj

« I am putting myself to the fullest possible use which is all, I think, that any conscious entity can ever hope to do » HAL (Heuristically programmed ALgorithmic computer) in "2001, A Space Odyssey"

No, this is not one of the solutions.
Thank you very much for the idea, but the trouble is…

This is not "converting numbers", not at all. The second member in this conversion is not a primitive type. This is a value of a statically unknown type, boxed. Boxed. That is, the object at the offset 0 is some managed reference; there is no point to "convert" it.

Exactly! Great point. (This is because I automated enumeration so much that I lost the manual enumerating skills .)
I'm still adding changes, hopefully minor ones. Later on, I will update other articles of this series to enumerate the full set of articles.