Introduction
Some time ago I published some controls to easily manage options in .net applications providing an automatic GUI for the Settings
values and some related facilities (more info in the article [^]). When developing that code, I needed a list which let me be notified of when items (OptionsPanel
s in that case) were added or removed from the collection. Because of generics collections in .net don't have nor such notification nor virtual methods, the only solution I found was to wrap an instance of a generic List
<>
in my class and then implement the generic IList
<>
interface and wrap all public methods of the ILis
t
<>
interface to the public methods of the variable whose instance was previously created. Then I found the need to do this type of wrapping in another couple of projects. So I asked myself how difficult would has been to automate this action to avoid manually redoing it each time. And here's the answer to my question, an add-in that does exactly what I was thinking about.
Using the add-in
Download the installer, unzip and install it. Start visual studio, open any kind of project (C#, VB or C++) and go to the declaration of a member variable (or property) inside a class. Right click on the variable name and select “Wrap variable”, if all is ok a form will open (exactly the image above). In the form there's a tree view in which you can select what properties, methods or events to wrap in the class. You have two additional options, “Surround code with Region”, which as the name suggests put a #region outlined to the generated code (or a comment for C++), and Prefix, which let you specify an optional prefix for generated elements (methods, properties and events), to avoid name collision.
Modifying the add-in
Download the project and open it with visual studio. I've hugely commented the code to allow easy modification of the features of the add-in, so I don't think there's the need to repeat in the article the same information. Ill give only a general overview of what is editable and “where”. Mainly you can do three things easily:
Apply a different filter to select what functions to load (and take in consideration) and which others not. For example, to allow friend
s classes (for C++) and internal
ones (for C#), I fill the tree view with public
s, protected
s and private
s. If you only code in C# for instance, you can remove the private
ones. You can do this in the FillFunctions
method of the Connect
class. See the code below:
private void FillFunctions(Dictionary<String, CodeElement>
funcs, CodeElement func,
CodeElement elm,
bool overloading)
{
if (
!String.IsNullOrEmpty(func.Name) &&
(
func.Kind == vsCMElement.vsCMElementFunction &&
(
( _CurrentLanguage != Language.VB &&
func.Name != elm.Name ) ||
( _CurrentLanguage == Language.VB &&
func.Name != "New" )
) &&
(
(_CurrentLanguage != Language.VB &&
!func.Name.StartsWith("~")) ||
( _CurrentLanguage == Language.VB &&
func.Name != "Finalize" )
)
) ||
func.Kind == vsCMElement.vsCMElementProperty ||
func.Kind == vsCMElement.vsCMElementEvent
)
{
[...]
if (!funcs.ContainsKey(name))
{
funcs.Add(name, func);
}
[...]
}
}
I thinks the comments are clear. Anyway as you can see there are some checks on the function and if all its ok, the function is added to the collection.
Change the appearance of elements (methods, properties and events) to be loaded in the three view; this is achievable by modifying the code of the LoadToTreeNodes
method, inside the Connect
class. See the code below:
private void LoadToTreeNodes(Dictionary<String, CodeElement>
funcs, TreeNodeCollection tnc)
{
[...]
TreeNode node = nodes.Add(name, cf.Name + "( " +
parmString.ToString() + " )");
node.ToolTipText = (cf.IsShared ? "static " : (cf
.CanOverride ? "virtual " : "")) +
cf.Type.AsString + " " + cf.Name + "( " +
parmStringComplete.ToString() + " )";
[...]
}
I've removed useless code to highlight the important one. As you can see a TreeNode
variable is created whose name is node
. This is a single node appearing in the tree view and representing an element (method, property, event). You can put all your logics here and edit the node as you wish (colors, images, tooltip text, ecc...).
Edit the generated code. In fact there's a method called for each language generated. If the language of the document is C# the method CreateCodeCSharp
is called; if the language is mc++ the method CreateCodeMC
is called; if the language is vb the method CreateCodeVB
is called; finally if the code is native c++ the method CreateCodeVC
is called. All four methods are hugely commented so there's no need to report here the same information. Look through the comments, each defines a new element added (for example the visibility, return type and name of a property, etc...). A note: because I personally (and I think a lot of people like me) don't use mc++, I've not included a full implementation of the mc++ code generation. Instead, the CreateCodeMC
calls CreateCodeVC
. This means that for mc++ only methods are supported, and aren't supported properties and events. In addition, because of I don't use (and don't know well so) the visual basic language, there could be probably some errors in the code generation of this language; if you found some of them report these in the comment section of the article providing the correct code to help other coders who could need it.
Points of Interest (problems)
When developing this add-in I encountered a problem, that is, it seems that visual studio doesn't represent the code model the same way in .net or in native c++. In fact while in .net its easy to step through types, accessing base as well, in c++ this is not possible. In fact, the element type returned is a special VC type and is not QueryInterface
able for the classic DOM elements of the code model. See the code to understand what I mean:
private void FillTypeFunctions(Dictionary<String, CodeElement>
funcs, CodeElement elm)
{
if (elm != null)
{
if (!_Types.ContainsKey(elm.FullName))
{
_Types.Add(elm.FullName, elm);
}
switch (elm.Kind)
{
case vsCMElement.vsCMElementStruct:
{
FillStructFunctions(funcs, (CodeStruct)elm);
break;
}
case vsCMElement.vsCMElementClass:
{
FillClassFunctions(funcs, (CodeClass)elm);
break;
}
case vsCMElement.vsCMElementVCBase:
{
break;
}
case vsCMElement.vsCMElementInterface:
{
FillInterfaceFunctions(funcs, (CodeInterface)elm);
break;
}
case vsCMElement.vsCMElementIDLCoClass:
{
break;
}
}
}
}
As you can see, in the switch
statement there is a case
vsCMElement.vsCMElementVCBase
which represents a base type element of another type and is not castable to CodeClass
or CodeInterface
or CodeStruct
. Probably theirs is some special element in the VCCodeModel
namespace, but to not loose too much time, I ignored it. In fact is not so common to need the base classes methods of the type of the variable, and if it is really needed the trick is to change directly (temporally) the type of the variable itself to one of his base types and then select “Wrap variable”. For example if you have a variable whose class is C
which inherits from B
which again inherits from A
, if the name of the variable is _Variable
, you can:
- Declare the variable as
C _Variable;
right-click on it, select the “Wrap variable” command and select the methods you need.
- Change the type from
C
to B
and re-do all the steps.
- Change the type from
B
to A
and re-do.
- Reset the type of the variable from
A
to C
.
Remember that this is needed only if you are coding in native c++.
Conclusion
So, that's all, I think. I hope you'll find it useful, bye!