http://www.codeproject.com/Articles/42799/Storm-the-world-s-best-IDE-framework-for-NET

Storm - the world's best IDE framework for .NET

gone000, 4 Feb 2010

   4.96 (79 votes)
 
Rate this:  
 
 
Create fast, flexible, and extensible IDE applications easily with Storm - it takes nearly no code at all!

Moonlite, my IDE that I created with Storm (still WIP)

Introduction

I'm currently making an IDE application called Moonlite. When I started coding it, I looked for good, already existing code for creating IDEs. I found nothing. So, when I was almost done with it (I'm not done with it yet, because of this framework taking all my time), I figured that I found it rather unfair that everyone should go through the same as I did for making such an application. (It took me 8 months of hard work - about 7 - 10 hours a day - to create this. Not because the main coding would've taken that long, but because I had to figure out how to do it and what was the most efficient solution.) So I started Storm, and now that it is finished, I'm going to present it to you! 

Notices

Please note that I did this of my own free will and my own free time. I would be happy if you could respect me and my work and leave me a comment on how to make it better, bug reports, and so on. Thank you for your time 

Using the Code

Using the code is a simple task; simply drag-drop from the toolbox when you have referenced the controls you want, and that should be it. However, for those who want a more in-depth tutorial, go to the folder "doc" in the package and open "index.htm".

How it Works

In this chapter, I will mostly cover docking, plug-ins, and TextEditor, since they are the most advanced ones. I will not cover Win32 and TabControl.

CodeCompletion

CodeCompletion relies on the TextEditor, and it really isn't as advanced as some may think. It's just a control containing a generic ListBox that can draw icons. The ListBox' items are managed by the CodeCompletion itself.CodeCompletion handles the TextEditor's KeyUp event, and in that, it displays the members of the ListBox depending on what the user has typed in the TextEditor.

UpdateCodeCompletion is now contained in the TextEditor library!

Every time it registers a key press, it updates a string containing the currently typed string by calling a methodGetLastWord(), which returns the word that the user is currently on. How a string is split up in words is defined in the TextEditor as 'separators'. Every time GetLastWord() is called, CodeCompletion calls the native Win32 function 'LockWindowUpdate' along with the parent TextEditor's handle to prevent flickering as the OS renderers the TextEditor/CodeCompletion.

Actually, CodeCompletion does this when it auto completes a selected item in the child GListBox, too. Every timeCodeCompletion registers a key that it doesn't recognize as a 'valid' character (any non-letter/digit character that isn't _), it calls the method SelectItem() along with a specific CompleteType.

Now, what is a CompleteType? You see, CompleteType defines how the SelectItem() will act when auto completing a selected item in the GListBox. There are two modes - Normal and Parenthesis. When Normal is used, the SelectItem() method removes the whole currently typed word; Parenthesis, however, removes the whole currently typed word except the first letter. This might seem strange, but it is necessary when, for example, the user has typed a starting parenthesis. You might find yourself having a wrong auto completed word sometimes, too - this is where you should use Parenthesis instead of Normal as the CompleteType. (You are able to define a custom CompleteType when you add a member item to CodeCompletion.)

Since the users define the tooltips of member items themselves, it is rather easy to display the description of items. When a new item is selected in the GListBox, a method updates the currently displayed ToolTip to match the selected item's description/declaration fields. Since a normal TreeNode/ListBoxItem wouldn't be able to have multiple Tags, I created the GListBoxItem, which also contains an ImageIndex for the parent GListBox'ImageList. The GListBoxItem contains a lot of values that are set by the user, either on initialization or through properties.

Each time the control itself or its tooltip is displayed, their positions are updated. The formula for the tooltip is this:Y = CaretPosition.Y + FontHeight * CaretIndex + Math.Ceiling(FontHeight + 2) for Y. The setting of X is simply CaretPositon.X + 100 + CodeCompletion.Width + 2. The formula for CodeCompletion's Y is the same as for the tooltip; however, X is different; X = CaretPosition.X + 100.

Docking

First, I will start out with a Class Diagram to help me out:

As you can see, there are a lot of classes. A DockPane can contain DockPanels, and DockPanels are the panels that are docked inside the DockPane. A DockPanel contains a FormDockCaption, and DockTab. When aDockPanel's Form property is set, the DockPanel updates the Form to match the settings needed for it to act as a docked form.

DockCaption is a custom drawn panel. It contains two Glyphs - OptionsGlyph and CloseGlyph - both inheriting the Glyph class, which contains the rendering logic for a general Glyph. The OptionsGlyph andCloseGlyph contain images that are supposed to have a transparent background. A lot of people use very complex solutions for this; however, I found a very, very simple and short solution:

 Collapse | Copy Code
/// <summary>
/// Represents an image with a transparent background.
/// </summary>
[ToolboxItem(false)]
public class TransImage
: Panel
{
#region Properties /// <summary>
/// Gets or sets the image of the TransImage.
/// </summary>
public Image Image
{
get { return this.BackgroundImage; }
set
{
if (value != null)
{
Bitmap bitmap = new Bitmap(value);
bitmap.MakeTransparent(); this.BackgroundImage = bitmap;
Size = bitmap.Size;
}
}
} #endregion /// <summary> /// Initializes a new instance of TransImage.
/// </summary>
/// <param name="image">Image that should
/// have a transparent background.</param>
public TransImage(Image image)
{
// Set styles to enable transparent background
this.SetStyle(ControlStyles.Selectable, false);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); this.BackColor = Color.Transparent;
this.Image = image;
}
}

As simple as that. A basic panel with transparent background, and of course, an Image property -Bitmap.MakeTransparent() does the rest. Panel is indeed a lovable control. As we proceed in this article, you'll find that I base most of my controls on Panel.

Well, the DockCaption handles the undocking of the DockPanel and the moving of the DockForm. Yeah,DockForm. When a DockPanel is undocked from its DockPane container, a DockForm is created, and theDockPanel is added to it. The DockForm is a custom drawn form which can be resized and moved, and looks much like the Visual Studio 2010 Docking Form.

Since the caption bar has been removed from the DockForm, the DockCaption takes care of the moving. This is where Win32 gets into our way - SendMessage and ReleaseCapture are used to do this.

When a DockPanel is added to a DockPane, and there's already a DockPanel docked to the side that the user wants to dock the new DockPanel, the DockPane uses the already docked DockPanel's DockTab to add the newDockPanel as a TabPage. The user can then switch between DockPanels.

The DockTab inherits the normal TabControl, and overrides its drawing methods. This means that it is completely customizable for the user and very flexible for us to use.

Plug-ins

The plug-ins library is one of the shorter; however, it is probably the most complex too. Since the PluginManagerclass has to locate dynamic link libraries, we should check whether they are actual plug-ins, check if they use the optional plugin attribute, and if they do, store the found information in an IPlugin, and add the found plug-in to the form given by the user if it's a UserControl.

So basically, most of these processes happen in the LoadPlugins method. However, the LoadPlugins method is just a wrapper that calls LoadPluginsInDirectory with the PluginsPath set by the user. Now, theLoadPluginsInDirectory method loops through all the files in the specific folder, checks whether their file extension is ".dll" (which indicates that the file is a code library), and then starts the whole "check if library contains plug-ins and check if the plug-ins have any attributes"-process:

This is done with the Assembly class, located in the System.Reflection namespace:

 Collapse | Copy Code
Assembly a = Assembly.LoadFile(file);

Then, an array of System.Type is declared, which is set to a.GetTypes(). This gives us an array of all types (class, enums, interfaces, etc.) in the assembly. We can then loop through each Type in the Type array and check whether it is an actual plug-in, by using this little trick:

 Collapse | Copy Code
(t.IsSubclassOf(typeof(IPlugin)) == true ||
t.GetInterfaces().Contains(typeof(IPlugin)) == true)

Yeah - simple - this simply can't go wrong. Well, we all know that interfaces can't get initialized like normal classes. So, instead, we use the System.Activator class' CreateInstance method:

 Collapse | Copy Code
IPlugin currentPlugin = (IPlugin)Activator.CreateInstance(t);

Boom. We just initialized an interface like we would with a normal class. Neat, huh? Now, we just need to setup the initialized interface's properties to match the options of the PluginManager and the current environment. This can by used by the creator of the plug-ins to create more interactive plug-ins. When we've done this, we simply addIPlugin to the list of loaded plug-ins in the PluginManager.

However, the plug-ins loaded by the PluginManager aren't enabled by default. This is where the user has to do some action. The user has to loop through all the IPlugins in the PluginManager.LoadedPlugins list, and call the PluginManager.EnablePlugin(plugin) method on it.

Now, if you have, for example, a plug-in managing form in your application, like Firefox, for example, you can use the PluginManager.GetPluginAttribute method to get an attribute containing information about the plug-in, if provided by the creator of the plug-in.

The way this works, is by creating an object array and setting it to the System.Type method,GetCustomAttributes(). The variable "type" is set to be the plug-in's Type property, which is set in the loading of a plug-in.

 Collapse | Copy Code
object[] pAttributes = type.GetCustomAttributes(typeof(Plugin), false);

Add it to the list of plug-ins:

 Collapse | Copy Code
attributes.Add(pAttributes[0] as Plugin);

And, when we're done looping, we'll finally return the list of found attributes.

TextEditor

Since I love my TextEditor, I will give you a little preview of what it's capable of. And it's not a little 

As you might have pictured already, this library has incredibly many classes/enums/interfaces/namespaces. Actually, there's so many that I won't put up a class diagram or explain the links between all the classes.

The TextEditor is basically just a container of the class TextEditorBase; it is actually TextEditorBase that contains all the logic for doing whatever you do in the TextEditor. The TextEditor only manages its fourTextEditorBases along with splitters when you've split up the TextEditor in two or more split views.

However, the TexteditorBase doesn't take care of the drawing; it simply contains a DefaultPainter field which contains the logic for rendering all the different stuff. Whenever drawing is needed, the TextEditorBase calls the appropriate rendering methods in the DefaultPainter. The DefaultPainter also contains a method namedRenderAll which, as you might've thought about already, renders all the things that are supposed to be rendered in the TextEditor.

Since the different highlighting modes are defined in XML sheets, an XML sheet reader is required. TheLanguageReader parses a given XML sheet and tells the parser how to parse each token it finds in the typed text in the TextEditor. A user does not use the LanguageReader directly; the user can either use theSetHighlighting method of a TextEditor, which is a wrapper, or use theTextEditorSyntaxLoader.SetSyntax method.

Unfortunately, I can't take credit for it all. I based it on DotNetFireball's CodeEditor; however, the code was so ugly, inefficient, and unstructured that it would probably have taken me less time to remake it from scratch than fix all these things. The code still isn't really that nice; however, it is certainly better than before.

Update: Since I have now gone through all source code and documented and updated it to fit my standards, I claim this TextEditor my own work. However, the way I do things are still the same as the original, therefore I credit the original creators.

I should probably mention that DotNetFireball did not create the CodeEditor. They simply took another component, the SyntaxBox, and changed its name. Just for your information.

Conclusion

So, as you can see (or, I certainly hope you can), it is a gigantic project, which is hard to manage, and I have one advice to you: don't do this at home. It has taken so much of my time, not saying that I regret it, but really, if such a framework already exists, why not use it? Making your own would be lame.

Not saying this for my own fault, so I can get more users, I'm saying this because I don't want you to go through the same things I did for such 'basic' things. (Not really basic, but stuff that modern users require applications to have.)

So yeah, I suppose that this is it. The place where you say 'enjoy' and leave the last notes, etc. Yeah, enjoy it, and make good use of it - and let me see some awesome applications made with this, please 

Planned Updates

【z】Storm - the world's best IDE framework for .NET的更多相关文章

  1. 【原】storm源码之一个class解决nimbus单点问题

    一.storm nimbus 单节点问题概述 1.storm集群在生产环境部署之后,通常会是如下的结构:从图中可以看出zookeeper和supervisor都是多节点,任意1个zookeeper节点 ...

  2. 【转载】Storm TickTuple 意外停止

    原文链接转自:http://woodding2008.iteye.com/blog/2328114 Storm的滑动窗口TickTuple通常用来控制bolt定制执行入库操作,使用过程中遇到了Tick ...

  3. 【Z】扩展阿里巴巴Java开发规约插件

    https://blog.csdn.net/u014513883/article/details/79186893 1.前言 工作中难免会遇到维护别人代码的情况,那么首先就得看懂别人写的代码.如果对方 ...

  4. 【转】分享两个基于MDK IDE的调试输出技巧

    我们在STM32开发调试过程中,常常需要做些直观的输出,如果手头没有相关的设备或仪器,我们可以使用 IDE自带的工具.这里分享两个基于MDK  IDE的调试输出技巧. 一.使用其自带的逻辑分析仪查看波 ...

  5. 亿级流量场景下,大型架构设计实现【2】---storm篇

    承接之前的博:亿级流量场景下,大型缓存架构设计实现 续写本博客: ****************** start: 接下来,我们是要讲解商品详情页缓存架构,缓存预热和解决方案,缓存预热可能导致整个系 ...

  6. 【原】storm源码之理解Storm中Worker、Executor、Task关系

    Storm在集群上运行一个Topology时,主要通过以下3个实体来完成Topology的执行工作:1. Worker(进程)2. Executor(线程)3. Task 下图简要描述了这3者之间的关 ...

  7. 【原】storm源码之storm代码结构【译】

    说明:本文翻译自Storm在GitHub上的官方Wiki中提供的Storm代码结构描述一节Structure of the codebase,希望对正在基于Storm进行源码级学习和研究的朋友有所帮助 ...

  8. 【原】Storm 入门教程目录

    Storm入门教程 1. Storm基础 Storm Storm主要特点 Storm基本概念 Storm调度器 Storm配置 Guaranteeing Message Processing(消息处理 ...

  9. 【原】Storm分布式RPC

    5. Storm高级篇 序列化 分布式RPC High level overview LinearDRPCTopologyBuilder Local mode DRPC Remote mode DRP ...

随机推荐

  1. 第4章 文件和目录(5)_贯穿案例2:mini shell(1)

    6. 贯穿案例2:mini shell(1) [阶段性任务]实现cd.pwd和quit命令 //job.h #ifndef __JOB_H__ #define __JOB_H__ //接收命令行参数 ...

  2. Goroutine(协程)为何能处理大并发?

    简单来说:协程十分轻量,可以在一个进程中执行有数以十万计的协程,依旧保持高性能. 进程.线程.协程的关系和区别: 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度. 线程拥有自己独 ...

  3. 文件系统性能测试--iozone

    iozone 一个文件系统性能评测工具,可以测试Read, write, re-read,re-write, read backwards, read strided, fread, fwrite, ...

  4. 使用SolrNet访问Solr-5.5.0

    由于今年年初刚发布的Solr-5.5.0,网上所能找到的资料少之又少,所以只能靠自己一点点摸索. 从某Hub上下载了SolrNet源码,按照教程提交文档或者查询均失败,无奈只得跟断点一点点差怎么回事. ...

  5. python装饰器(docorator)详解

    引言: 装饰器是python面向对象编程三大器之一,另外两个迭代器.生成器只是我现在还没有遇到必须使用的场景,等确实需要用到的时候,在补充资料:装饰器在某些场景真的是必要的,比如定义了一个类或者一个函 ...

  6. HiveThrift

    Hive具有一个可选的组件叫HiveServer或HiveThrift,其允许通过指定端口访问Hive.Thrift是一种软件架构,用于跨语言的服务开发. hive最常用的访问方式是采用cli访问,不 ...

  7. MYSQL中利用select查询某字段中包含以逗号分隔的字符串的记录方法

    首先我们建立一张带有逗号分隔的字符串. CREATE TABLE test(id int(6) NOT NULL AUTO_INCREMENT,PRIMARY KEY (id),pname VARCH ...

  8. Spring MVC 数据绑定流程

    DataBinder 数据绑定 入参,校验,格式化, ConversionService: 进行数据类型转换和数据格式化 Validators 进行数据合法性的校验, 把结果放入BindingResu ...

  9. 通过maven 上传jar 到nexus3,cong nexus3下载jar

    nexus是一种常见的maven私服软件. 网上介绍的都是nexus2的使用,下面是最新版nexus3的使用方式. 首先需要从官网下载nexus3的包,很卡. 下载好以后解压会有两个文件夹:nexus ...

  10. sql server判断是否为null

    sql server 替换null:isnull(arg,value) 如:select isnull(price,0.0) from orders ,如果price为null的话,用0.0替换 与n ...