- 浏览: 4711153 次
- 性别:
- 来自: 济南
最新评论
-
wahahachuang8:
GoEasy 实时推送支持IE6-IE11及大多数主流浏览器的 ...
服务器推送技术 -
pdztop:
inffas32.asm(594) inffas32.asm( ...
zlib 在 Visual Studio 2005 下编译失败的解决办法 -
myangle89:
这个方法有效果,但还是绕了一大圈。另外:如果每次这样使用,会造 ...
利用 Spring 与 Log4J 巧妙地进行动态日志配置切换并立即生效 -
lsw521314:
亲,请把用到的包贴出来好么?这版本问题搞得我头大······· ...
lucene MMAnalyzer 实现中文分词 -
guji528:
多命令执行:cmd /k reg delete "H ...
REG Command in Windows XP - Windows XP REG命令的作用和用法
http://www.codeproject.com/csharp/AutoDiagrammer.asp
What This Article Is
AutoDiagrammer : Features
So How Does It All Work "In a Nutshell"
Step1 : Opening A File
Step2 : Determining If A File Is A Valid CLR Type
Step3 : Create The Tree And Classes
Step3a : Should Type Be Included
Step4 : Individual Class Analysis (Reflection)
Step5 : User Selects Which Classes Should Be Shown On Diagram
What Else Can It Do ?
Customizing Whats Shown On The Diagram
Saving A Diagram
Printing support
Auto Update Support
Reflector Addin
So What Do You Think ?
Conclusion
History
Introduction
This article is about using reflection. For those of you that don't know what reflection is, it is the ability to obtain information about a Type of object without really knowing anything about the object type that is being dealt with. For example one could simply ask the current Type of object if it supports a certain method name, if it does, the method can be called. This may sound strange but it is a very powerful technique. Lets suppose that I simply want to look at what methods a class supports, well that is also easily achieved using reflection. .NET allows developers to leverage reflection in many ways.
As I have stated this article is all about creating a class diagram using reflection. That means the attached application (codenamed AutoDiagrammer) does not even need source files, it doesn't care about the source files, as it just doesn't need them. It just wants to be pointed at a CLR assembly (dll) or a CLR application (exe). That's it. No source files required at all. So it can be seen as a companion to Lutz Roeders Reflector. It simply provides the class diagramming capabilities that are not present within Lutz Roeders Reflector.
I have tried to make a useful product: to this end the following features are supported:
Customization of what is shown on the class diagram
Show interfaces [Yes / No]
Show constructor parameters [Yes / No]
Show field types [Yes / No]
Show method arguments [Yes / No]
Show method return values [Yes / No]
Show property types [Yes / No]
Show events [Yes / No]
Show enumerations
Show delegates [Yes / No]
Number of columns to use for the generated diagram (Number between 1-5)
Class background start color
Class background end color
Class border color
Accessability modifier selection (Public only / Public and Static / All)
Automatically drawn class association lines
Automatically drawn generalization (inheritence) arrows
Expand individual sections of a class (Constructors / Fields / Properties / Methods / Events may all be collapsed / expanded individually)
Expand entire class
Class representation as similar to Visual Studio 2005 look and feel as possible
Allow saving of diagram to the following image formats (Bmp, Emf, Exif, Gif, Jpeg, Png)
So those are the main features. These will all be explained in more detail in the following sections
So I guess by now you are wondering how all this actually happens, aren't you? Well, let me try and explain how I did all this. Let me start with a simple step-by-step account of what goes on. This will probably help, before I delve into the nitty gritty workings.
Determine if the selected file is a valid CLR type. (The class DotNetObject.cs within the attached application does this). If the input file is not a valid CLR Type tell the user, and do nothing more. Determine if the file is a "System" namespace assembly, if it is, alert the user and do nothing more. If we get past both these checks proceed to step 3.
Add each allowable type (obtained in sub-step 3 below) to a treeview on the main form (frmMain.cs), and also create a new class object (ucDrawableClass.cs) for each allowable Type seen.
Use reflection to examine all the Types contained within the input file. Only include the Types from the current input file that are not part of the "System" namespace. Including "System" types, would take forever, and is not what we are trying to do. There is lots of good documentation available for the Microsoft "System" namespace objects. That's my opinion anyway. Anyway we digress, so for each allowable Type create a new class object (ucDrawableClass.cs) passing it the Type to represent. See step 4.
When a new class object (ucDrawableClass.cs) is created, it will analyze the Type that it is supposed to represent and extract all Constructors / Fields / Properties / Methods / Events information using reflection and store these details.
The user selects what classes they would like to view from the treeview (right click) and then a new class drawing panel (ClassDrawerContainerPanel.cs) is created which lays out the classes (ucDrawableClass controls) that the user requested and draws the association lines (ucAssociationDrawer.cs) for any available association lines.
That's the basic operation of what is going on. But there is much more that may be of interest to the average codeproject user, so lets look at each of these steps in some more detail and also look as some of the more advanced features mentioned earlier.
I am not going to explain every line of the code for the main form (frmMain.cs), as it is fairly standard win forms stuff. I will however explain the interesting parts.
{
//proceed
}
else
{
//not a valid CLR so tell user and do nothing more
}
So how does the DotNetObject.isDotNetAssembly() method actually work? Well, it works as follows:
/// <summary>
/// Returns true if the file specified is a real CLR type,
/// otherwise false is returned.
/// False is also returned in the case of an exception being caught
/// </summary>
/// <param name="file">A string representing the file to check for
/// CLR validity</param>
/// otherwise false is returned.
/// False is also returned in the case of an exception being
/// caught</returns>
public static bool isDotNetAssembly(String file)
{
uint peHeader;
uint peHeaderSignature;
ushort machine;
ushort sections;
uint timestamp;
uint pSymbolTable;
uint noOfSymbol;
ushort optionalHeaderSize;
ushort characteristics;
ushort dataDictionaryStart;
uint[] dataDictionaryRVA = new uint[16];
uint[] dataDictionarySize = new uint[16];
Stream fs = new FileStream(@file, FileMode.Open, FileAccess.Read);
{
//PE Header starts @ 0x3C (60). Its a 4 byte header.
fs.Position = 0x3C;
peHeader = reader.ReadUInt32();
//Moving to PE Header start location...
fs.Position = peHeader;
peHeaderSignature = reader.ReadUInt32();
//We can also show all these value, but we will be
//limiting to the CLI header test.
machine = reader.ReadUInt16();
sections = reader.ReadUInt16();
timestamp = reader.ReadUInt32();
pSymbolTable = reader.ReadUInt32();
noOfSymbol = reader.ReadUInt32();
optionalHeaderSize = reader.ReadUInt16();
characteristics = reader.ReadUInt16();
Now we are at the end of the PE Header and from here, the
PE Optional Headers starts...
To go directly to the datadictionary, we'll increase the
stream's current position to with 96 (0x60). 96 because,
28 for Standard fields
68 for NT-specific fields
From here DataDictionary starts...and its of total 128 bytes.
DataDictionay has 16 directories in total,
doing simple maths 128/16 = 8.
So each directory is of 8 bytes.
btw, the 15th directory consist of CLR header! if its 0,
it is not a CLR file :)
*/
(Convert.ToUInt16(fs.Position) + 0x60);
fs.Position = dataDictionaryStart;
for (int i = 0; i < 15; i++)
{
dataDictionaryRVA[i] = reader.ReadUInt32();
dataDictionarySize[i] = reader.ReadUInt32();
}
if (dataDictionaryRVA[14] == 0)
{
fs.Close();
return false;
}
else
{
fs.Close();
return true;
}
}
catch (Exception ex)
{
return false;
}
finally
{
fs.Close();
}
}
//load the assembly and check it is not System namespace
//we don't want to be here all year
Assembly ass = Assembly.LoadFrom(f.FullName);
if (ass.FullName.StartsWith("System"))
{
Program.ErrorBox("System namespace assemblies not allowed");
}
//valid assembly that not a System one, so proceed
#region valid CLR type, non-system namespace
else
{
//set UI state correctly
......
......
//create a new BackgroundWorker thread to do the scanning
BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler
(bgw_RunWorkerCompleted);
bgw.RunWorkerAsync(ass);
}
#endregion
The new BackGroundWorker is created and the bgw_DoWork method is the method that starts the analysis process. So lets have a look at that method.
/// <summary>
/// The background worker thread start doing work, calls the
/// internal StartAnylsisProcess() method
/// </summary>
/// <param name="e">the event args</param>
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
Assembly a = (Assembly)e.Argument;
//start the analysis process, making sure to marshall
//to correct thread for GUI property change operation, which
//will require the thread to be on the same thread as the handle
//to the control
if (this.InvokeRequired)
{
this.Invoke(new EventHandler(delegate
{
StartAnylsisProcess(a);
}));
}
else
{
StartAnylsisProcess(a);
}
}
/// Loop through each Type in the current Assembly
/// adding each to the forms treeview by calling the
/// internal addTypesToTree() method
/// </summary>
/// <param name="a">The current Assembly being examined</param>
private void StartAnylsisProcess(Assembly a)
{
//loop through each Type in the current Assembly
//adding each to the forms treeview
foreach (Type t in a.GetTypes())
{
addTypesToTree(_tvnRoot, t.Name, t.Namespace, t);
}
}
/// <summary>
/// If namespace isn't empty, add the namspace to the current node and
/// <see cref="Namespaces">Namespaces</see> object. Then check the type
/// is actualy one that can be added to the diagram, could be System
/// (which is not allowed). Then add the type to the namespace by creating
/// a new <see cref="ucDrawableClass">ucDrawableClass</see> object,
/// for each allowable type
/// </summary>
/// <param name="typename">the current typename</param>
/// <param name="nspace">the current namspace</param>
/// <param name="t">the Type to examine</param>
String nspace, Type t)
{
try
{
//if namspace isn't empty, add the namspace to the current node
//and to the treeview and the Namspaces object
if (nspace != null)
{
if (!nspace.Trim().Equals(String.Empty))
{
//1st add the new namespace
if (!TreeContainsNode(nd, nspace))
{
nd.Nodes.Add(new TreeNode(nspace, 1, 1));
_nspaces.addNameSpace(nspace, null);
}
//to the diagram, could be System (which is not allowed)
if (IsWantedForDiagramType(t))
{
//now add the type to the namespace,
//by getting namespace from the list of nodes 1st
foreach (TreeNode tn in nd.Nodes)
{
if (tn.Text == nspace)
{
TreeNode currTypeNode = new TreeNode
(typename, 2, 2);
tn.Nodes.Add(currTypeNode);
//create a new ucDrawableClass
//and add it to the current namespace
ucDrawableClass dc = new ucDrawableClass(t);
_nspaces.addNameSpace(nspace, dc);
[" + t.Name + "]";
pbStatus.Visible = true;
Refresh();
Application.DoEvents();
}
}
}
}
}
}
catch (Exception ex)
{
Program.ErrorBox(ex.Message);
}
#endregion
new Dictionary<string, List<ucDrawableClass>>();
Recall earlier that I mentioned that certain Types would not be allowed, so how is this done? Notice in the code above, there is a call to a
This method call is what determines if the current Type being examined should be included both as a class, and to the treeview. Lets just have a quick look at this method.
/// <summary>
/// Returns true if the Type specified by the t parameter is a valid
/// diagrammable type. e.i. Not System namespace, and is one that the
/// user wants on the current diagram
/// </summary>
/// <param name="t"></param>
/// diagrammable type. e.i. Not System namespace, and is one that the
/// user wants on the current diagram</returns>
private bool IsWantedForDiagramType(Type t)
{
//don't really want user to trawl the standard System namespaces
if (t.Namespace.StartsWith("System"))
return false;
if (!Program._AllowEnumOnDiagram)
{
if (t.BaseType != null)
{
if (t.BaseType.FullName.Equals("System.Enum"))
{
return false;
}
}
else
{
return false;
}
}
//should MulticastDelegates be shown, on final diagram
if (!Program._AllowDelegatesOnDiagram)
{
if (t.BaseType != null)
{
if (t.BaseType.FullName.Equals("System.MulticastDelegate"))
{
return false;
}
}
else
{
return false;
}
}
//check for ApplicationSettingsBase
if (t.BaseType != null)
{
if (t.BaseType.FullName.Equals
("System.Configuration.ApplicationSettingsBase"))
{
return false;
}
}
//if we get to here its an allowable Type
return true;
}
Recall from above, that the main form (frmMain.cs) creates new ucDrawableClass objects. So what is one of those ucDrawableClass objects. Well in answer to that, it's a fairly in-depth user control, that employs both custom painting and also contains child controls. To get our head around what a ucDrawableClass control looks like, consider the following figure.
/// Stores the type provided as an internal field and
/// calls the AnalyseType() internal method. And also
/// creates several <see cref="ucExpander">ucExpander </see>
/// Constructors,Methods details. Finally an overall
/// collapse / expand image is created
/// </summary>
/// <param name="t">The Type to analyze</param>
public ucDrawableClass(Type t)
{
...
AnalyseType();
...
}
Fields
Properties
Interfaces
Methods
Events
So how are these lists created. Reflection Reflection Reflection Reflection, its all about Reflection. Lets have a look shall we.
/// <summary>
/// Returs a string which is the name of the type in its full
/// format. If its not a generic type, then the name of the
/// t input parameter is simply returned, if however it is
/// a generic method say a List of ints then the appropraite string
/// will be retrurned
/// </summary>
/// <param name="t">The Type to check for generics</param>
private string getGenericsForType(Type t)
{
string name ="";
if (!t.GetType().IsGenericType)
{
//see if there is a ' char, which there is for
//generic types
int idx = t.Name.IndexOfAny(new char[] {'`','\''});
if (idx >= 0)
{
name=t.Name.Substring(0,idx);
//get the generic arguments
Type[] genTypes =t.GetGenericArguments();
//and build the list of types for the result string
if (genTypes.Length == 1)
{
//name+="<" + genTypes[0].Name + ">";
name+="<" + getGenericsForType(genTypes[0]) + ">";
}
else
{
name+="<";
foreach(Type gt in genTypes)
{
name+= getGenericsForType(gt) + ", ";
}
if (name.LastIndexOf(",") > 0)
{
name = name.Substring(0,
name.LastIndexOf(","));
}
name+=">";
}
}
else
{
name=t.Name;
}
return name;
}
else
{
return t.Name;
}
}
/// Analyses the current Type (which was supplied on construction)
/// and creates lists for its Constructors, Fields, Properties,
/// Interfaces, Methods, Events to provide to these lists to
/// <see cref="ucExpander">ucExpander </see>controls
/// </summary>
{
List<MethodInfo> propGetters = new List<MethodInfo>();
List<MethodInfo> propSetters = new List<MethodInfo>();
#region Constructors
//do constructors
foreach (ConstructorInfo ci in
_type_to_Draw.GetConstructors(Program.RequiredBindings))
{
if (_type_to_Draw == ci.DeclaringType)
{
string pDetail="";
//so that the association lines for this class can be
//obtained, and possibly drawn on the container
ParameterInfo[] pif = ci.GetParameters();
foreach (ParameterInfo p in pif)
{
string pName=getGenericsForType(p.ParameterType);
pName = LowerAndTrim(pName);
if (!_Associations.Contains(pName))
{
_Associations.Add(pName);
}
pDetail = pName + " " + p.Name + ", ";
cDetail += pDetail;
}
if (cDetail.LastIndexOf(",") > 0)
{
cDetail = cDetail.Substring(0,
cDetail.LastIndexOf(","));
}
cDetail += ")";
//do we want long or short field constructor displayed
if (Program._FullConstructorDescribe)
{
_Constructors.Add(cDetail);
}
else
_Constructors.Add(_type_to_Draw.Name + "( )");
}
}
#endregion
#region Fields
//do fields
foreach (FieldInfo fi in
_type_to_Draw.GetFields(Program.RequiredBindings))
{
if (_type_to_Draw == fi.DeclaringType)
{
//add all the field types to the associations List, so that
//the association lines for this class can be obtained, and
//possibly drawn on the container
string fName=getGenericsForType(fi.FieldType);
fName = LowerAndTrim(fName);
if (!_Associations.Contains(fName))
{
_Associations.Add(fName);
}
//do we want long or short field description displayed
if (Program._IncludeFieldType)
_Fields.Add(fName + " " + fi.Name);
else
_Fields.Add(fi.Name);
}
}
#endregion
#region Properties
//do properties
foreach (PropertyInfo pi in
_type_to_Draw.GetProperties(Program.RequiredBindings))
{
if (_type_to_Draw == pi.DeclaringType)
{
// add read method if exists
if (pi.CanRead) { propGetters.Add(pi.GetGetMethod(true)); }
// add write method if exists
if (pi.CanWrite) { propSetters.Add(pi.GetSetMethod(true)); }
//add all the property types to the associations List, so that
//the association lines for this class can be obtained, and
//possibly drawn on the container
pName = LowerAndTrim(pName);
if (!_Associations.Contains(pName))
{
_Associations.Add(pName);
}
//do we want long or short property description displayed
if (Program._IncludePropValues)
_Properties.Add(pName + " " + pi.Name);
else
_Properties.Add(pi.Name);
}
}
#endregion
#region Interfaces
//do interfaces
if (Program._IncludeInterfaces)
{
Type[] tiArray = _type_to_Draw.GetInterfaces();
foreach (Type ii in tiArray)
{
_Interfaces.Add(ii.Name.ToString());
}
}
#endregion
#region Methods
//do methods
foreach (MethodInfo mi in
_type_to_Draw.GetMethods(Program.RequiredBindings))
{
if (_type_to_Draw == mi.DeclaringType)
{
string mDetail = mi.Name + "( ";
string pDetail="";
//do we want to display method arguments, if we do create the
//appopraiate string
if (Program._IncludeMethodArgs)
{
ParameterInfo[] pif = mi.GetParameters();
foreach (ParameterInfo p in pif)
{
//add all the parameter types to the associations List,
//so that the association lines for this class can
//be obtained, and possibly drawn on the container
string pName=getGenericsForType(p.ParameterType);
pName = LowerAndTrim(pName);
if (!_Associations.Contains(pName))
{
_Associations.Add(pName);
}
pDetail = pName + " " + p.Name + ", ";
mDetail += pDetail;
}
if (mDetail.LastIndexOf(",") > 0)
{
mDetail = mDetail.Substring(0,
mDetail.LastIndexOf(","));
}
}
mDetail += " )";
//add the return type to the associations List, so that
//the association lines for this class can be obtained, and
//possibly drawn on the container
string rName=getGenericsForType(mi.ReturnType);
//dont want to include void as an association type
if (!string.IsNullOrEmpty(rName))
{
rName=getGenericsForType(mi.ReturnType);
rName = LowerAndTrim(rName);
if (!_Associations.Contains(rName))
{
_Associations.Add(rName);
}
//do we want to display method return types
if (Program._IncludeMethodReturnType)
mDetail += " : " + rName;
}
else
{
//do we want to display method return types
if (Program._IncludeMethodReturnType)
mDetail += " : void";
}
//work out whether this is a normal method, in which case add it
//or if its a property get/set method, should it be added
if (!Program._ShowPropGetters &&
propGetters.Contains(mi)) { /* hidden get method */ }
else if (!Program._ShowPropSetters &&
propSetters.Contains(mi)) { /* hidden set method */ }
else {
_Methods.Add(mDetail);
}
}
}
#endregion
#region Events
//do events
foreach (EventInfo ei in
_type_to_Draw.GetEvents(Program.RequiredBindings))
{
if (_type_to_Draw == ei.DeclaringType)
{
//add all the event types to the associations List, so that
//the association lines for this class can be obtained, and
//possibly drawn on the container
string eName=getGenericsForType(ei.EventHandlerType);
eName = LowerAndTrim(eName);
if (!_Associations.Contains(eName))
{
_Associations.Add(eName);
}
if (Program._IncludeEventType)
_Events.Add(eName + " " + ei.Name);
else
_Events.Add(ei.Name);
}
}
#endregion
}
#endregion
}
Hopefully you can see that these 6 lists are then simply used to create 6 new ucExpander controls, which are then positioned at the correct X/Y positions within the current ucDrawableClass object.
We now have a treeview with ONLY valid namespaces and ONLY valid classes created. We also have a nice NameSpaces object which contains a Dictionary of strings (for namespaces) and for each string a list of ucDrawableClass objects (for the classes). The list of ucDrawableClass objects, are created and are ready and waiting to be placed on a suitable drawing canvas.
So the story so far is as described above. But still no diagram.
Right click on the namespace in the tree (with at least one class selected) OR use the button on or the menu item on the main form (frmMain.cs)
This is shown below (just for fun, this tree shows all the classes for the AutoDiagrammer.exe (that's this article code) which were reflectively obtained)
Use the menu to view diagram
#region Constructor
/// <summary>
/// Creates a new AssociationDrawer object using the parameters provided and
/// then calls the internal Draw() method
/// </summary>
/// <see cref="ClassDrawerContainerPanel">
/// ClassDrawerContainerPanel</see>, so that this class
/// can draw an association
/// line on the panel where the classes are held</param>
/// <param name="ucdSrc">The source
/// <see cref="ucDrawableClass">class</see></param>
/// <see cref="ucDrawableClass">class</see></param>
/// <param name="genericSpace">The generic space between
/// classes used by the
/// <see cref="ClassDrawerContainerPanel">ClassDrawerContainerPanel</see>
/// when laying out the controls</param>
ucDrawableClass ucdDest,int genericSpace)
{
this._ucdSrc = ucdSrc;
this._ucdDest = ucdDest;
this._GenericSpace = genericSpace;
this._g = g;
//do the draw
GetDirectionAndDraw();
}
#endregion
And an example of how one of the associations is drawn, say North.
The association rules -> method calls
It can be seen that the association rules make use of the previously set properties for each ContainerRow and ContainerColumn of the source and destination ucDrawableClass objects. The properties ContainerRow and ContainerColumn, were set when the ClassDrawerContainerPanel first layed out all the required ucDrawableClass objects. So now it's just a case of creating the rules, almost verbatim.
/// <summary>
/// Works out which direction the association line should be based
/// on the source <see cref="ucDrawableClass">class</see> ContainerRow/
/// ContainerColumn and the destination
/// <see cref="ucDrawableClass">class</see>
/// ContainerRow/ ContainerColumn. When the direction is found,
/// one of the Draw
/// direction methods is called, for example DrawNorth(), DrawSouth()
/// </summary>
{
//NORTH = Row is 1 below the current row, but same column
if (_ucdDest.ContainerRow == _ucdSrc.ContainerRow - 1 &&
_ucdDest.ContainerColumn == _ucdSrc.ContainerColumn)
{
DrawNorth();
}
//SOUTH = Row is 1 above the current row, but same column
if (_ucdDest.ContainerRow == _ucdSrc.ContainerRow + 1 &&
_ucdDest.ContainerColumn == _ucdSrc.ContainerColumn)
{
DrawSouth();
}
//EAST = Column is 1 above the current column, but same row
if (_ucdDest.ContainerColumn == _ucdSrc.ContainerColumn + 1 &&
_ucdDest.ContainerRow == _ucdSrc.ContainerRow)
{
DrawEast();
}
//WEST = Column is 1 below the current column, but same row
if (_ucdDest.ContainerColumn == _ucdSrc.ContainerColumn - 1 &&
_ucdDest.ContainerRow == _ucdSrc.ContainerRow)
{
DrawWest();
}
//NORTH-EAST = Row is 1 or more below and the column is 1 or more above
if (_ucdDest.ContainerRow <= _ucdSrc.ContainerRow - 1 &&
_ucdDest.ContainerColumn >= _ucdSrc.ContainerColumn + 1)
{
DrawNorthEast_DrawSouthEast();
}
//SOUTH-EAST = Row is 1 or more above and the column is 1 or more above
if (_ucdDest.ContainerRow >= _ucdSrc.ContainerRow + 1 &&
{
DrawNorthEast_DrawSouthEast();
}
//NORTH-WEST = Row is 1 or more below and the column is 1 or more below
if (_ucdDest.ContainerRow <= _ucdSrc.ContainerRow - 1 &&
_ucdDest.ContainerColumn <= _ucdSrc.ContainerColumn - 1)
{
DrawNorthWest_DrawSouthWest();
}
//SOUTH-WEST = Row is 1 or more above and the column is 1 or more below
if (_ucdDest.ContainerRow >= _ucdSrc.ContainerRow + 1 &&
_ucdDest.ContainerColumn <= _ucdSrc.ContainerColumn - 1)
{
DrawNorthWest_DrawSouthWest();
}
//NORTH-NON-DIRECT = Row is 2 or more below the current row,
//but same column
if (_ucdDest.ContainerRow <= _ucdSrc.ContainerRow - 2 &&
_ucdDest.ContainerColumn == _ucdSrc.ContainerColumn)
{
DrawNorthNonDirect_DrawSouthNonDirect();
}
//SOUTH-NON-DIRECT = Row is 2 or more above the current row,
//but same column
if (_ucdDest.ContainerRow >= _ucdSrc.ContainerRow + 2 &&
{
DrawNorthNonDirect_DrawSouthNonDirect();
}
//EAST-NON-DIRECT = Column is 2 or more above the current column,
//but same row
if (_ucdDest.ContainerRow == _ucdSrc.ContainerRow &&
_ucdDest.ContainerColumn >= _ucdSrc.ContainerColumn+2)
{
DrawEastNonDirect_DrawWestNonDirect();
}
//WEST-NON-DIRECT = Column is 2 or more below the current column,
//but same row
if (_ucdDest.ContainerRow == _ucdSrc.ContainerRow &&
_ucdDest.ContainerColumn <= _ucdSrc.ContainerColumn - 2)
{
DrawEastNonDirect_DrawWestNonDirect();
}
}
/// <summary>
/// </summary>
private void DrawNorth()
{
// /\
// |
// |
int xStart = 0;
int xEnd = 0;
if (_ucdDest.Right <= _ucdSrc.Right)
{
xStart = _ucdDest.Right - 20;
xEnd = _ucdDest.Right - 20;
}
else
{
xStart = _ucdSrc.Right - 20;
xEnd = _ucdSrc.Right - 20;
}
int yStart = _ucdSrc.Top;
int yEnd = _ucdDest.Bottom;
//create a dasked line
Pen p = new Pen(new SolidBrush(Color.Black));
p.DashStyle = DashStyle.Dash;
//and drawn the association
_g.DrawLine(p,new Point(xStart, yStart), new Point(xEnd, yEnd));
//draw the end arrow
_g.DrawLine(p, new Point(xEnd, yEnd), new Point(xEnd - 5, yEnd + 10));
_g.DrawLine(p, new Point(xEnd, yEnd), new Point(xEnd + 5, yEnd + 10));
}
So that's the basics covered. But I have not yet shown you how to modify what is shown on the diagram, or how to save a diagram. So if you still want more, let us continue.
To customize what is actually going to be included on the diagram, there is an extra settings form (frmSettings.cs) which is accessible using the toolbar on the main form (frmMain.cs) or the menu of the main form (frmMain.cs).
Show constructor paremeters [Yes / No]
Show field types [Yes / No]
Show method arguments [Yes / No]
Show method return values [Yes / No]
Show get method for existing property as method [Yes / No]
Show set method for existing property as method [Yes / No]
Show property types [Yes / No]
Show events [Yes / No]
Show enumerations
Show delegates [Yes / No]
Class background start color [ Color ]
Class background end color [ Color ]
Class border color [ Color ]
Accessability modifier selection (Public only / Public and Static / All)
Let's have a look at an example class or two, with some of these extra items turned on.
I decided that although handy, this tool would be pretty useless, unless people could actually save diagrams.To this end, the application supports saving to the following image formats : Bmp, Emf, Exif, Gif, Jpeg, Png. In order to save a diagram there is an additional saving form (frmSave.cs) which is available from either the toolbar or the menu, from the main form (frmMain.cs).
/// <summary>
/// by the input parameters
/// </summary>
/// <param name="filename">the filename to save the diagram to</param>
/// <param name="imgFormat">the image format to save the diagram as</param>
/// <returns></returns>
{
Cursor.Current = Cursors.WaitCursor;
int bmpSrcWidth = pnlFlowClasses.MaxSize.Width;
int bmpSrcHeight = pnlFlowClasses.MaxSize.Height;
//create a new ClassDrawerContainerPanel (which will not be shown
//on the form)
ClassDrawerContainerPanel pnl = new ClassDrawerContainerPanel();
pnlFlowClasses.SuspendLayout();
Rectangle newBounds = new Rectangle(0, 0, bmpSrcWidth, bmpSrcHeight);
pnl.Height = bmpSrcHeight;
pnl.Width = bmpSrcWidth;
pnl.Bounds = newBounds;
pnl.BackColor = Color.White;
pnl.SetBounds(0, 0, bmpSrcWidth, bmpSrcHeight);
pnl.ClassesToDraw = pnlFlowClasses.ClassesToDraw;
pnl.LayoutControls();
Bitmap bmpNew = null;
Graphics gfx = null;
//to the save file name
//the Bitmap object maintains a lock on the physical file,
//so we need to use another dummy Bitmap
//to hold the original image, so that the lock creates
//by the original image saving process can be released,
//then we can savethe dummy Bitmap contents
//back to the original image and conduct the save.
//http://blog.vishalon.net/
//http://support.microsoft.com/?id=814675
try
{
SrcBmp = new Bitmap(bmpSrcWidth, bmpSrcHeight);
pnl.DrawToBitmap(SrcBmp, newBounds);
bmpNew = new Bitmap(SrcBmp.Width, SrcBmp.Height);
gfx = Graphics.FromImage(bmpNew);
gfx.DrawImage(SrcBmp, new Rectangle
(0, 0, bmpNew.Width, bmpNew.Height),
0, 0, SrcBmp.Width, SrcBmp.Height, GraphicsUnit.Pixel);
// As original SrcBmp keeps lock on file,
// we need to copy the original image
// to a second image, and then release the lock on the image file.
// Of course,image from the initial image file
// is now existing on the new Graphics object
// which points to the second image,
//ready to copy back to the original image
SrcBmp.Dispose();
SrcBmp = bmpNew;
gfx.Dispose();
//do the save (now that the original lock has been dealt with)
SrcBmp.Save(filename, imgFormat);
SrcBmp.Dispose();
pnlFlowClasses.ResumeLayout();
return true;
}
catch (Exception ex)
{
if (SrcBmp != null) { SrcBmp.Dispose(); }
if (bmpNew != null) { bmpNew.Dispose(); }
if (gfx != null) { gfx.Dispose(); }
GC.Collect();
return false;
}
}
There is one final form, which is the About window (frmAbout.cs), where people can contact me, should they wish to. Probably not, I'm betting.
There is now support for printing.
Thanks to "Click Once" deployment within Visual Studio 2005. The application is now auto updating. Simply go to the following url publish.htm and the following page will be shown in the browser
Yes there is now support for reflector addin functionality, all largely due to a fellow named "Andre Seibel", who actually got the dll information from Reflector into my code. I managed to create a Reflector plugin OK, I just couldn't get the Assembly data out of Reflector. But "Andre Seibel" did, so thanks Andre.
Here is what to do: unzip and copy the AutoDiagrammer.dll at the top of this article to your Reflector installation directory. Open Reflector, and then the add-in menu, then search for the AutoDiagrammer.dll, and add this as a valid Reflector AddIn. Then under the tools menu, there will be a new AutoDiagrammer menu. Click that and away you go.
Well that's actually about it. I hope that by me just detailing the interesting parts of the application, that I have not bored you all to death. This application has taken me about two part-time weeks to complete, and I have done quite a bit of thinking about how to do things, so I hope this is reflected in the way that I have tried to discuss the design with you good folk. There will always be better ways, however, this is the journey that I took. So I thought why not share that with you guys. But finally it's up to you guys to let me know what you think.
I would just like to ask, if you liked the article please vote for it, as it allows me to know if the article was at the right level or not.
I have quite enjoyed constructing this article. I hope you lot liked it. I think its fairly useful. Even if it is to just help you to understand something about reflection. I like reflection and have more articles to write about it, but I'm going to be hitting the XAML and LINQ/DLINQ/XLINQ stuff 1st. So expect more articles in those areas before I write some more AI and reflection. Well that really is the end now, so thanks for listening to my ranting.
v1.10 08/04/07: Attempted to fix NullPointerException that some user were having with Reflector AddIn Dll.
v1.9 01/04/07: Fixed one small issue with line drawing maths, that I noticed when running code as Reflector AddIn
Another new fearture is that, I have now added tooltips to the classes, such that when the user hovers a mouse over the class, its associations are shown as a tooltip, so its easier to follow the associations that are drawn
Also fixed the problem with v1.8 where the desktop version would not draw the classes any more. There is now one app, zip at top. And depending on if you want the standalone or reflector version, you simply change the build options in visual studio. WindowsApplication with Program as start class for stand alone app. Or as a ClassLibrary for Reflector addin. Though the stanalone app can be installed using the auto update link at the top of this article and the Reflector addin, is also available as a seperate download at the top of this article.
v1.8 31/03/07: Its now available as as Add-In for reflector
v1.7 22/03/07 : Added for control of what is shown for method on diagram:
Allow users to pick whether for a given Property, whether the associated get/set reflected methods should also be shown on the class diagram. As the displaying of the associated get/set reflected methods is really redundant, as the Property is already shown. This new solution uses a bit of code as supplied by codeprojecter AlwiNus. So thanks AlwiNus. Well done. I was actually trying to solve a completely different issue, or thought people were asking for something else. Doh.
v1.6 15/03/07: Added support for generics:
v1.5 11/03/07: Further CodeProject user comments include. Namely the following:
Auto updating is now supported
Allow customisation of class drawing colors (maybe for better printing)
Allow customisation of class data shown based on accessability modifiers (Public / Private / Static)
v1.4 06/03/07 : Further codeproject user comments include. Namely the following :
Zoom and settings form now allow scroll with mouse, Focus() was called in wrong form event. Now all ok
Exit shortcut key changed to CTRL + X on main form
Tools menu changed to say "Settings"
"User settings" menu changed to say "Configure settings"
Additonal setting added to settings page, to allow user to adjust the number of columns that will be shown on the Diagram
When the user selects Ok on the settings page, an automatic re-scan is done of the current file, such that the new settings are used
When the diagram is initially shown all the classes are shown in the collapsed state
If the user doesnt click and Namespace child nodes, application assumes that all children of the current node are to be drawn
There is now the ability to hide the treeview from a menu, or by the veretical pin button on the new vertical panel (grey one youll see it)
v1.3 04/03/07: Fixed a bug namely the following :
v1.2 03/03/07: Fixed a bug namely the following :
There is now a new Zoom form. But dont be expecting the image to work as the initial diagram goes. Its only an image respresentation of the original diagram, so you can zoom. But you cant collapse / expand the controls on the Zoom form.
v1.1 02/03/07: Codeproject initial release comments incorporated. Namely the following:
Settings form now has Ok and Cancel buttons
The main form now allows saving from both a new menu item, and a new toolbar button (Though you must still have a Namespace treeview node selected, as this is used to tell the application what to actually draw)
The main form can now be re-sized to any size the user wishes, and may be minimized or maximized
The saving of the diagram has been totally changed, now all native C# code. Thanks James Curran
Any classes that do not have a Namespace, are now added to a new treeview node called "No-Namespace Classes"
v1.0 01/03/07: Initial issue
相关推荐
1. "100-Reflective-Class-Diagram-Creation-Tool.pdf":这很可能是关于该工具的详细文档或教程,可能包含了如何使用工具、工具的工作原理、示例等信息。 2. "AutoDiagrammer.zip":这个名字暗示这可能是一个自动...
Reflective Ecore Model Diagram Editor是基于GMF的Eclipse插件,它仅使用元模型(例如.ecore和.xsd文件)为任何EMF模型文件提供图形编辑器。 您不需要任何.gmfgraph,.gmftool,.gmfmap或.gmfgen文件。
《深入理解Reflective DLL Injection》 DLL(动态链接库)注入是一种常见的系统级技术,它允许一个进程在不修改或不知情的情况下加载另一个进程中的DLL。Reflective DLL Injection是DLL注入的一种高级形式,它利用...
经典paper:Reflective DLL Injection以及其代码实现,具体可以参考下面两个链接: 1.http://blog.csdn.net/GaA_Ra/archive/2011/06/06/6528359.aspx 2.http://www.harmonysecurity.com/ReflectiveDllInjection.html
在当前的移动网络发展中,"Accelerating Service Creation and Deployment" 是一个关键的议题,它意味着通过创新技术提高服务创建和部署的速度与效率。随着科技的进步,未来的移动网络将变得可编程,这为服务创新...
描述弹性波界面反射率,折射率曲线,示例参数是铝-有机玻璃,钢-空气界面,输入选择横波,纵波,输出为信号幅度和输入信号幅度比值
A novel reconfigurable partially reflective surface (PRS) antenna is presented in this paper. The beam scanning ability is realized by employing a reconfigurable PRS structure and a phased array as ...
《Unity3D中的Simple LWRP Reflective Shaders详解》 Unity3D作为一款强大的游戏开发引擎,其在图形渲染方面提供了丰富的工具和技术。其中,Lightweight Render Pipeline(LWRP)是Unity为了满足高性能、低内存占用...
reflective-clothes-detect-dataset、helemet_detectio_reflective-clothes-detect-yolov5
该项目为基于Python与Shell开发的reflective-clothes-detect设计源码,共包含41个文件,其中包括14个yaml配置文件、14个Python源代码文件、5个JPEG图片文件、4个Shell脚本文件、1个Markdown文件、1个Python交互式...
独立的VNC服务器编译成一个Reflective DLL
这个"**goreflect - Golang Reflective DLL加载.zip**"可能包含一个用于动态链接库(DLL)加载的Go语言实现,特别强调了反射的应用。下面将详细解释相关知识点。 1. **反射(reflection)** Go语言中的反射通过`...
在IT领域,软件开发是一个充满创新与探索的过程,其中“反射塔”(Reflective Towers)是一种独特的技术实践,它涉及到软件设计、编程语言和考古学的交叉应用。这个概念源于对3-Lisp、布朗、金发和黑色等元素的综合...
In order to ease the creation of these maps the package includes a tool for generating procedurally these maps. Technical aspects: ▲ Optimized and polished prefabed models ▲ 8k & 4k Environment ...
All-reflective optical systems, due to their material absorption and low refractive index, are used to create the most suitable devices in extreme ultraviolet lithography (EUVL). In this letter, we ...
文章"Resonator-based reflective metasurface for low-frequency underwater acoustic waves"阐述了如何设计和优化这种超表面,以在低频段(通常低于几千赫兹)实现高效反射。研究者可能通过调整共振器的共振频率,...
abstractproduct.h is C++ template-based library providing reflective object creation mechanism (similar to Java s Class.forName("ClassNameHere").newInstance()). It does so by static initialization of ...
基于激光雷达实现三边定位算法记录系列博文的ROS功能包代码,已经过优化和重构,和原文差别较大,不过基本思路一样。包内有两个功能包,一个是反光柱定位的主要功能包,一个是反光柱管理节点功能包,通过对接口的...
标题中的“VMIC.rar_Reflective Memory_site:www.pudn.com_反射内存”提示我们,这个压缩包可能包含了关于VMIC(Virtual Machine Interface Controller,虚拟机接口控制器)使用反射内存技术的相关文档。描述中提到...
《关于RDBAC(Reflective Database Access Control Policies)的课件》 RDBAC,即反射数据库访问控制,是一种创新的数据库访问控制机制,它的核心特点是“反射性”,即允许系统(一组数据对象)能够对自身进行推理...