Click here to Skip to main content
Email Password   helpLost your password?

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.

Update: CodeCompletion 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 method GetLastWord(), 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 time CodeCompletion 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 Form, DockCaption, and DockTab. When a DockPanel's Form property is set, the DockPanel updates the Form to match the settings needed for it to act as a docked form.

A 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 and CloseGlyph 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:

/// <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 the DockPanel 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 new DockPanel 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 PluginManager class 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, the LoadPluginsInDirectory 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:

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:

(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:

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 add IPlugin 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.

object[] pAttributes = type.GetCustomAttributes(typeof(Plugin), false);

Add it to the list of plug-ins:

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 four TextEditorBases 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 named RenderAll 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. The LanguageReader 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 the SetHighlighting method of a TextEditor, which is a wrapper, or use the TextEditorSyntaxLoader.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

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralUpdate approaching
Theodor Storm Kristensen
10:09 30 Jan '10  
OK, so I know that I haven't really been keeping my promises, but please bear with me, I've just got a new job and school's been keeping me busy. Furthermore I was injured from a training skiing.

Well, now I would just like to tell you guys that I am fixing the last two minor bugs in the text editor before the release. There are A LOT of changes, and some of them are rather huge:

- Implemented inheritance in language definition files. (E.g Html can inherit Css and JavaScript)
- Added more syntax highlighting functionality. (Parses regular expressions correctly)
- ALL code is now documentated.
- I have gone through all source code files and optimized them to match my coding standards. I have also raised efficiency.
- Added the 'Indent' type in language definition files.
- Changed the default style of the text editor from a Visual Studio 2008 like style to a copy of the Visual Studio 2010 text editor.

And a lot more...

I think I will have the release ready either before february or in early february... stay tuned.
I will not promise anything as I have broken a lot of them already.

Thanks,
Theo
GeneralRe: Update approaching
Theodor Storm Kristensen
11:02 30 Jan '10  
Aaaaand the first bug is fixed! 1 bug remaining!
GeneralSorry for neglecting you guys
Theodor Storm Kristensen
6:55 19 Dec '09  
Yeah, I have been really, really busy with school, and then suddenly the snow came and I, of course, couldn't miss a chance of skiing Smile
Well, the update is really, really close. I need 1 more class (which is 3500 lines, lol), then I'm finished improving the code. I will then have to do the RegisterMargin method, but it'll be quick.

I'm also implementing a ruler margin (though in a separate library), so for you who thought you needed it, worry no more Thumbs Up
Thanks to all supporters, you're keeping me working on this thing Cool
GeneralExcellent!
Marcelo Ricardo de Oliveira
3:46 17 Dec '09  
Thanks a lot for sharing it, Theodor, it's such a nice work. Btw, do you intend to maintain this project all by yourself?
5 stars indeed!

Take a look at full source code C# Snooker game here in Code Project.

GeneralRe: Excellent!
Theodor Storm Kristensen
9:30 17 Dec '09  
Thanks dude! Smile
Well, unless someone else want to help me, I will. Also, if no one wants to help I'll probably just make a WindowsFormsHost wrapper for the Wpf version of the TextEditor (since I'm lazy and busy)

Smile
GeneralAmazing.
Eber Ramirez
7:08 14 Dec '09  
Thanks for your very hard work done and for share it.
You are great.
Wink
GeneralRe: Amazing.
Theodor Storm Kristensen
6:50 19 Dec '09  
Thanks, cheers Cool
GeneralKeep up the good work Theodor!
Ben Kaizi-Lutu
8:21 11 Dec '09  
I am glad you are doing this and not listening to all the detractors who say, you shouldn't build this because something else similar already exists. That is one of the ways that innovation is stifled. Good for you. The process of building IDE's, Text Editors etcs is not trivial and for you to have pulled it off to any degree means you have worked hard and learnt a lot. You may have found easier ways of doing things than they are being currently done. That is progress, that is innovation.

For those who have said you should have just joined the SharpDevelop team. Phewy!! Would they have received your input if you didn't have anything to show that you had experience building IDEs?

This is a great step for you in your career. Please keep working, keep learning and keep innovating..

Cheers.
Ben.
GeneralRe: Keep up the good work Theodor!
Theodor Storm Kristensen
6:51 19 Dec '09  
Thanks! Also, very wise post, I should start quoting you Smile

Thanks again for the nice words Thumbs Up
GeneralA small suggestion. Hope can help.
reborn_zhang
6:28 7 Dec '09  
Energy is limited.

If you develop a VS like framework. No one would remember you.

But,

If you can develop a VS like framework purely on WEB. Everyone would remember you.

I think transfer your docking/texteditor/plugin... to web, would help others more.

Coding on web broswer maybe more funny.
GeneralRe: A small suggestion. Hope can help.
reborn_zhang
6:30 7 Dec '09  
If you have the ability to be the first one(I think you can). Do not be the second.
GeneralRe: A small suggestion. Hope can help.
Pavel Urbancik
4:51 5 Feb '10  
Web is not everything, for many tasks (programming included) it's absolutely horrible environment.
So .. it might be cool "showoff" project, but of no practical value.
GeneralRe: A small suggestion. Hope can help.
Theodor Storm Kristensen
4:56 5 Feb '10  
And it has already been done - http://www.phpanywhere.net
GeneralWhat about SharpDevelop?
Qwertie
6:45 6 Dec '09  
How does this project compare to the IDE tools in SharpDevelop? SharpDevelop is designed so that you could create your own IDE with the code, if you wanted to, and there are both WinForms (SharpDevelop 3.x) and WPF (SharpDevelop 4.x) versions. There are tutorials here on CodeProject for the Text Editors:

Using ICSharpCode.TextEditor[^]

Using AvalonEdit (WPF Text Editor)[^]

For an IDE you also need some kind of manager for add-ins, and the SharpDevelop Team also offers one of those, although I found it hard to understand...

Building Applications with the SharpDevelop Core[^]

Another thing an IDE needs is a docking panel control, and I'm not sure what SharpDevelop uses, but I've heard DockPanel Suite works pretty well: http://sourceforge.net/projects/dockpanelsuite/[^] (WinForms only)

So the question is, what makes your new framework "the world's best" in comparison to these existing tools? Were you even aware of these other tools?
GeneralRe: What about SharpDevelop?
Theodor Storm Kristensen
9:11 6 Dec '09  
Because the others aren't frameworks - they are just applications, not controls, where you can use their source codes.
I'm currently making an WPF version of the framework.

I was also aware of the DockPanel Suite, and it is definitely very good, but I just wanted to create my own. I figured I could just aswell implement it in the package, people don't have to use all the libraries in the package, you know Wink

I also found the way that the TextEditors of SharpDevelop implemented CodeCompletion was very overcomplicated. Not to mention that the next version of my CodeCompletion control, both for WinForms and for WPF (the WPF version will be much more good looking, somewhat like the Expression Blend 3 CodeCompletion), will be much more advanced and flexible.

Thank you for your interest.
GeneralRe: What about SharpDevelop?
Daniel Grunwald
15:04 5 Feb '10  
Theodor Storm Kristensen wrote:
Because the others aren't frameworks - they are just applications, not controls, where you can use their source codes.

SharpDevelop is both a set of reusable libraries and an application. We try to make libraries available as standalone, without any references to the SharpDevelop core or other libraries.

Theodor Storm Kristensen wrote:
I also found the way that the TextEditors of SharpDevelop implemented CodeCompletion was very overcomplicated. Not to mention that the next version of my CodeCompletion control, both for WinForms and for WPF (the WPF version will be much more good looking, somewhat like the Expression Blend 3 CodeCompletion), will be much more advanced and flexible.

Did you look at both ICSharpCode.TextEditor and AvalonEdit or only at the old ICSharpCode.TextEditor?

I did a lot of work to simplify the code completion API in AvalonEdit (yes the old "provider"-model was horrible), but if you have even better ideas, I'd like to hear them!
GeneralRe: What about SharpDevelop?
Theodor Storm Kristensen
23:16 5 Feb '10  
I think I looked at the old ICSharpCode.TextEditor. I should try looking at the new one Smile
I actually implemented the CodeCompletion library into the TextEditor library in this new version, and changed the lame .Add(new CompletionData (...)) to a provider model - a simple one, though, but a lot like yours.

How are you doing the new provider model?
GeneralStatus update for the next release
Theodor Storm Kristensen
10:02 28 Nov '09  
Hey guys, just wanted to give you a little update on the progress of the next release.

I'm currently rewriting the TextEditor from scratch - the methods are the same, but the code is much more structured, documentated and efficient. I'm about 60% done with it. I will focus on it the next week so I can release the next version for your satisfactions.

New features include:
- faster XML sheet parsing;
- you can now create custom margins by inheriting from BaseMargin and call the TextEditor's AddMargin method;
- you are now able to override drawing methods -- this means that you can, for example, override the selection drawing method and draw a gradient rounded rectangle instead;
- drawing events are now implemented -- you can now add actions when the TextEditor eg. has finished drawing the rows.
- several bug fixes.

There will probably be more features implemented, but they are not in it yet. So be patient Smile

Other changes:
- several efficiency increases.
- a lot of new features in Storm.Plugins.dll, such as loading single plugins.
- updated documentation.

I think I'll have the next update in this upcoming week, stay tuned. Smile
GeneralJust a quick note ...
Muaddubby
10:36 23 Nov '09  
Your toolbar shows the Paste, Cut and Copy buttons (in that order), but I think the standard is usually Cut, Copy, Paste Smile


GeneralRe: Just a quick note ...
Theodor Storm Kristensen
20:32 23 Nov '09  
Yeah, for normal toolbars, but not for the Ribbon Smile Atleast not in Office Word 2007 Smile
GeneralRe: Just a quick note ...
Muaddubby
3:14 24 Nov '09  
Ok, haven't seen the Ribbon so I'll take your word for it. Good work Smile


GeneralAbsolut nice thing... [modified]
tim_mcgwyn
3:23 17 Nov '09  
Hi,

finally - really (<--) nice work.

Texteditor:
1. ...4 TextEditorBases along with splitters...
Maybe you spend a little time to separate the horizontal & vertical splitters - at the moment - they can only switch(both) on or off. better Way is that each splitter can be selected on or off Smile

2. Highlighting -> ...textEditor.SetHighlighting(SyntaxLanguage.CSharp);... maybe it can be places as a propertie-entry Smile

and here some more suggestions about your Textbox
-> vertical and horizontal rulers
-> Contextmenu for Breakpoints (for selecting the Icons you included)
-> EOL Line(s) (eg. a "lightgray" line which shows the user -> "Your Cursor are now at position 80....)
-> Add a Marker for Textchanging. This will make your Texteditor more usable for programer Smile


Docking:
1. It would be nice if you spend some time to show the place-icons (eg. VS2008). so it will be more usefull Smile


Your Tool/Application Moonlite
In this App you shows some Ribbon-Controls - do you publish that to? And what will your Moonlite do?

I know how uncomfortable it is to write documentation but - maybe you find some time to update your doc Smile

Regards
Tim.

(I'll start to be a fan of your work)

modified on Tuesday, November 17, 2009 10:36 AM

GeneralRe: Absolut nice thing...
Theodor Storm Kristensen
10:36 17 Nov '09  
TextEditor:
1. Sorry but I don't really get this - elabroate? Smile

2. Definitely will do Smile

Vertical and horizontal rulers:
Don't really get either, sorry :s

Contextmenu (...):
I will add that Smile

EOL Line(s) (...):
Already included Smile

Add a marker (..):
Don't really get this xD

Docking:
1. Hmmz?

Moonlite:
No, I won't publish my Ribbon Control. I think that if everyone gets access to it, it will be overused and it will lose its worth.

I'm already updating my documentation to be better Smile

Also I'm thinking about rewriting the TextEditor completely and make it much more extensible. (Include a "BaseMargin" class that allows users to create custom margins for example)

Also I think I'm gonna make a WPF version too when the WinForms version is completely done and bugfree. (Maybe not bugfree, but when I get all my planned features implemented)

> (I'll start to be a fan of your work)

Thanks, I'm very honered Smile
GeneralUnload/Load Plugins
krn_2k
3:08 17 Nov '09  
Nice project, well done.

A common requirement for many types of IDE's is that they provide editing features allowing users make new plugins to extend the same IDE. Unfortunately for most IDE frameworks the only way to allow users to edit/test/update cycle plugins is to "restart the IDE between edits", or let plugin versions pile up in memory until its exhausted (easily done if developing a larger plugin). Depending on what the IDE is for, this is usually not an adequate solution.

Do you have plans to allow plugins to be loaded, unloaded and updated without these common problems?
GeneralRe: Unload/Load Plugins
Theodor Storm Kristensen
10:31 17 Nov '09  
Well, in my IDE I was able to unload plugins easily. But I'll take a look at that AppDomain thing to see if there's a better way.


Last Updated 4 Feb 2010 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010