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

Sample Image - DIY-Intellisense.gif

Quick start

Download the project, compile and run it. Go to the menu and load %systemroot%\Microsoft.NET\Framework\v1.xxx\system.dll. Type System then a "." in the richtextbox. A listbox of classes/namespaces will appear.

Note: The namespace icon is used for both namespaces and types with no icons, this is laziness on my part.

Introduction

If you're a programmer and not familiar with intellisense, the chances are you've been hiding in a cave on a dialysis machine for the past 5 years. Intellisense is a Microsoft trademark, but the concept is available by dozens of code editors. In simple terms, it's a drop down list that appears when you type a recognized word into your code editor. For example, typing in System, then a "." inside Visual Studio .NET will show a drop down list of types for the System namespace. Another example can be found in Macromedia Dreamweaver - typing in a particular HTML tag and then a space will cause a list of attributes for that HTML tag to appear.

A fuzzy discussion about the history of intellisense can be found here.

In this article and the accompanying code, I'll demonstrate how to implement an intellisense-like system (I'll call it auto completion from now on) for a RichTextBox, triggered by the user pressing the "." key.

The complete project has the following features:

I also threw the following in for good measure:

All of this doesn't look too much on virtual-paper, but the source code is almost 900 lines (including the form component layout code). I'll go through the code bit-by-bit, but first, the requirements.

The requirements

The project is a simple form with a RichTextBox and a main menu. The RichTextBox control hasn't been subclassed in any way, it's had its font changed to Courier and that's it.

The basic requirement is that when a user types a word, and then a ".", and the word is a known word, a list of items appear in a ListBox, underneath and to the right of the "." they just typed. From this, they can select an item from the ListBox, whose text is then pasted onto the RichTextBox, completing the word they were typing.

The form also has a TreeView, used for storing the items to be looked up, a ListBox, to display the items, and a text field which is used as cheap and cheerful tooltip for the method parameter lists.

Pressing the magic "." key

The RichTextBox's OnKeyDown event checks for an OemPeriod (I'm assuming this works on all machines being used). When a dot is pressed, the previous word is retrieved using the getLastWord() method.

This word is then checked to see if it exists inside the tree that we use for storing our lookups (described next). If it's found, the ListBox is populated with these items. populateListBox() manages this, returning a boolean for whether it populated any items or not. After this, the ListBox is positioned.

Luckily, the RichTextBox features a method that makes this task simple. Using the RichTextBox's GetPositionFromCharIndex(), we can get a Point struct for the current caret position. Using this, we position the ListBox's X coordinate according to the .'s and the Y is the same, but the height of the font is added (I tweaked it slightly, removing an extra 2 pixels for Courier); it is then positioned to appear underneath the ".". Visual Studio .NET positions the box above or below the current line, depending on the line position you're at in the text editor (the box appears above if you're on the last line of the text editor). I haven't added this feature, but I imagine it wouldn't be too hard to do.

The lookup tree

As I mentioned, all the items that the autocomplete looks for are stored in a TreeView. For the type of code completion I'm implementing - Namespaces/classes/methods - storing the items in a tree makes perfect sense. For something like HTML code completion, this would also be the ideal candidate, the root nodes could be the core set of HTML tags, and the nodes underneath would be the attributes. For a flat list, the TreeView could possibly be got rid of, and the ListBox used on its own.

When I started the project, I started to create my own tree implementation for storing the items. I then realized there was no point doing this as .NET provided the structure for me in the form of a TreeView. It's got the overhead of a being a control, however it allows you to easily add and edit items inside an IDE like Visual Studio .NET, Borland C# builder, #develop, etc., making the job quicker.

Code completion behavior

Once the "." is pressed and the drop down list of items appear, the following logic is applied:

All of this is checked in the RichTextBox's OnKeyDown event.

Finding an item in the tree

Once the previous word that was typed is found, the TreeView is searched to see if the word can be found. As the word can contain a full namespace, it is broken down according to the dots, and the FullPath property of the node is checked against a concatenated string. This is done inside a recursive function. When a node is found, this is stored in a member variable, which is then used to populate the ListBox with all the child nodes. The nodes are sorted by their text by creating an array of a custom type, which implements IComparable. This array is populated from the TreeNode, then sorted and the ListBox is populated using it.

Obviously, sorting the items each time they're populated is quite a slow way around, a preferred method would be to sort the tree when it's populated. A sorteable TreeView would be a whole new article though (has anyone implemented one?!), this suffices for now.

Autocompleting the text

This is a fairly simple procedure - everything before the dot is stored in a string, and everything after the dot is stored in another string, and a new string has the selected ListBox item's text appended in the middle of the two. The RichTextBox's text is then replaced with this new text. I haven't tested this method with large amounts of text, it may require customizing the RichTextBox if flickering starts to occur.

Extras

To demonstrate the autocompletion, additional 2 methods are in the source, for reading an assembly and populating the tree with its types. The readAssembly() method loads an assembly in and cycles through all its types, adding nodes to the tree for namespaces, classes, methods, fields and events. Each type uses addMembers() to have its fields, methods and events added as nodes.

Each ListBox item features an icon to indicate what type it is - this type is stored inside each TreeNode's Tag property. The Tag property also stores a method's parameter list as a string, so we assume the node is a method if the Tag property is a string. The display of icons in the ListBox is done using the GListBox class.

As mentioned above, when a method is typed and the left bracket '(' key is pressed, a tooltip is displayed with the parameter list for the method. I couldn't find any free custom tooltip implementations for this, so I used the displaying of a TextBox with the details in, positioning it the same way the ListBox of items is positioned.

Conclusion

I don't want to over-bloat this article by going into great depths about each method, so I'll stop here. Hopefully it's useful to people. I want to point out that it's proof of concept code rather than any finished product; I haven't tested the TreeView to see how well it weathers with 1000s of nodes, and there are minor things I could improve in it (given a team of developers, a big paycheck and the knowledge that the biggest company in the world was backing me!)

Known Bugs

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralUse of code
szb_szb
21:39 6 Aug '09  
Can I use your code in my commercial software?
GeneralUsing Intellisense
Mr. Badr CHOUFFAI
5:37 15 Jul '09  
Hi.
Can you give us an exemple how to use Intellisense feature.

thanks in advance.
Generalthis is awesome!
firehak
17:11 16 Apr '09  
this is great, I have to manage different router configurations at school, and creating an "intellisense" editor seemed like a great idea to me. Big Grin
QuestionCommercial use?
sanme98
7:13 31 May '08  
Hi Marschills,

May I know is it I can use your code as source code editor for my commercial software?

Thank you.
GeneralI wonder if it would be OK...
therearefartoomanybens
13:28 11 Dec '07  
I'd like to borrow inspiration and a little code from your GListBox... if that would be alright?

I'm steadily expanding some more usable WinForm components, I will opensource and codeproject it when its finished - but an autocompleting RichEdit is a so-darn-useful component to have in the toolbox it'd be criminal not to elaborate on your work.

You should have P/Invoked GetCaretPos too silly!

... Roll eyes but I have done that for you hahaha...

CSharp-on-Rails here we go!
QuestionDo not work !
Yeurl_2007
0:53 20 Mar '07  
I m trying to play with the demo, after load the System.dll, nothing append after "."

Sample i wrote: System.
:/

I hva try this demo project on tree computer:
vista professional with vs2k5 sp1
xp professional whith vs2k3
windows 2k pro whtih only runtime of .net framework 1.1 and 2.0

The problem still here after compiling the project... please help !

Yeurl
AnswerRe: Do not work !
Wouter Demuynck
0:30 4 May '07  
If the "." on your keyboard requires the shift key to be pressed, try just hitting the key without shift. I think the code assumes qwerty layout.
GeneralHow to fix the tab key not being handled..
illium
21:26 13 Dec '06  
regarding the known bug the author lists, it's a simple matter to fix it..

in richTextBox1_KeyDown, add the line:

e.SuppressKeyPress = true;

after

e.Handled = true;

and bingo no more annoying extra tab.

kudos for a nice looking demo.

_illium


-----
"It's 5:50 a.m., Do you know where your stack pointer is?"

GeneralRe: How to fix the tab key not being handled..
Vegio
4:01 2 Feb '09  
Or even


if (e.KeyCode == Keys.Tab)
{
if (this.listBoxAutoComplete.Visible)
e.SuppressKeyPress = true;
}

if ( e.KeyData == Keys.OemPeriod )
{
// The amazing dot key

if ( !this.listBoxAutoComplete.Visible)
{
// Display the member listview if there are
// items in it
if ( populateListBox() )
{
//this.listBoxAutoComplete.SelectedIndex = 0;

// Find the position of the caret
Point point = this.richTextBox1.GetPositionFromCharIndex(richTextBox1.SelectionStart);
point.Y += (int) Math.Ceiling(this.richTextBox1.Font.GetHeight()) + 2;
point.X += 2; // for Courier, may need a better method

this.statusBar1.Text = point.X + "," + point.Y;
this.listBoxAutoComplete.Location = point;
this.listBoxAutoComplete.BringToFront();
this.listBoxAutoComplete.Show();
}
}
else
{
if (this.listBoxAutoComplete.SelectedIndex > -1)
{
selectItem();
this.listBoxAutoComplete.Visible = false;
richTextBox1_KeyDown(sender, e);
}
typed = "";
}
else if ( e.KeyCode == Keys.Up )
{


in the beginning of the _KeyDown handler. This way, intended tabs don't get suppressed. Also, when pressing dot again while a listitem is highlit, it inserts it via the recursive call...


Vegio
QuestionClipping
CyclonicEvent
0:44 11 Nov '06  
Hi. Thanks for sharing a great piece of work. Have you seen any clipping of the icons to the left of the text within the auto complete boxes? Any ideas on how best to solve?

Thanks
AnswerRe: Clipping
CyclonicEvent
5:03 11 Nov '06  
Okay, so I've found a way around this. Probably not the best method, but I have simply added a line to the GListBox constructor increasing the list box line height as below:-

this.ItemHeight = this.ItemHeight + 5


Generalintellisense for db
rizomatosa
8:43 16 Oct '06  
great works! thanks to share!
Answare about db usage:
Can i think to use this code with a special dll where i stored database informations (table, columns etc..)?
an idea how build this dll?

thanks&ciao
General=)
Chris Nevill
7:19 14 Jan '06  
This is a really interesting project.
Any news of that control?!

I'm currently writing a C# script plugin for a media center application (www.jrmediacenter.com)
I'm very short of time, so don't have much time to look into this
but it would be very useful if the plugin could do intellisense!

I've just tried to run your sample, however it falls over
at line 223 of Form Main with a security Exception?
Any ideas?
Cheers
Chris
GeneralRe: =)
smallguy78
1:23 14 Nov '06  
I'm afraid I've really busy doing an MCAD and CS degree at present (and a full time job) so I've had to put my pet coding projects on hold for now.

Can you quote the security exception?
GeneralRe: =)
Chris Nevill
1:39 14 Nov '06  
Thanks for your reply even if it was 10 months late =)
It's a long time since I've looked at this.
I'll have to look into it again. I've got a feeling I did get
it up and running eventually.

Cheers
Chris
GeneralCapturing tab
smallguy78
7:09 24 Mar '05  
I'll update this article and my others over the summer months when it's raining(sorry, too busy at the moment), to try to make this into a control. In the meantime, here's how you can implement tab autocomplete with the richtextbox:


// Change richTextBox1 so it is a TabCaptureRichTextBox
public class TabCaptureRichTextBox : RichTextBox
{
public event CancelEventHandler TabPressed;
private const int WM_KEYDOWN = 0x100;

protected override bool ProcessCmdKey(ref System.Windows.Forms.Message msg, Keys keyData)
{
if ( this.TabPressed != null )
{
if ( msg.Msg == WM_KEYDOWN )
{
if ( keyData == Keys.Tab )
{
CancelEventArgs e = new CancelEventArgs();
this.TabPressed(this,e);

// Will this work when asynchronously called?
return e.Cancel;
}
}
}

return base.ProcessCmdKey (ref msg, keyData);
}
}


// Inside FormMain class definition...

// Inside InitializeComponent()
this.richTextBox1.TabPressed += new System.ComponentModel.CancelEventHandler(this.toBoxTo_TabPressed);


private void richTextBox1_TabPressed(object sender, System.ComponentModel.CancelEventArgs e)
{
if ( this.listBoxAutoComplete.Visible )
{
this.selectItem();
e.Cancel = true;
}
}

GeneralThanks
danielprc
5:00 22 Mar '05  
I do need this.
GeneralUsing a form instead
jonathan_dickinson
22:55 23 Feb '05  
I think a more elegant solution is to use a form that is always on top, but this is much more difficult. I tried it and ran into many problems, can somebody who has a bit more knowledge of interop have a bash at this one...

Anyway, the form simply housed a list box, but I had focus problems, particularily when the mouse was used on the scroll bar of the list box, I could not detect that the form had come into focus so that I could focus the main window.

We need a form so that it can overlap other windows, or leave the parent window. Anyway, please can someone have a bash at this...
GeneralOne more thing
jonathan_dickinson
23:07 23 Feb '05  
I tried adding variable detection, but it was amazingly slow.

Anyway, here is how it worked (pseudocode):

Apon Pressing '.' :

depth = 0
Loop from start to current pos
{
if currentchar = '{' increase depth
if currentchar = '}' remove variables at depth and decrease depth
if the next word it a type
{
add the variable name to a collection, along with it's depth
}
}

Now the collection contains all the variables...

But it is so slow...
Any ideas?
GeneralRe: Using a form instead
reinux
16:27 28 Feb '05  
I'm not too sure how you could make the form constantly keep focus; what do you need to do that for?

The main problem I'm having right now with using a form is that I can't figure out which property (properties) to use to calculate/determine the character's relative position to the screen. Any ideas on that?
GeneralRe: Using a form instead
reinux
2:57 22 Apr '05  
Hey, I got it! It's really complicated, but here it is.
Here's to show the form initially:


[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int ShowWindow(IntPtr hWnd, int nCmdShow);

readonly static int SW_SHOWNA = 8;

public new void Show()
{
ShowWindow(this.Handle, SW_SHOWNA);
base.Show();
}


Here's to intercept keyboard and mouse events. uiIntercept is a Control object referencing the text box, and I have the KeyDown, KeyPress and MouseWheel events implemented.


public readonly static int WM_KEYDOWN = 0x0100;
public readonly static int WM_KEYUP = 0x0101;
public readonly static int WM_MOUSEWHEEL = 0x020A;

private void uiIntercept_KeyDown(object sender, KeyEventArgs e)
{
if(!Visible)
return;
if(e.Control || e.Alt)
return;
switch(e.KeyCode)
{
case Keys.Up:
case Keys.Down:
case Keys.PageUp:
case Keys.PageDown:
case Keys.Escape:
break;
default:
return;
}

Message msg = Message.Create(this.Handle, WM_KEYDOWN, new IntPtr((int)e.KeyCode), IntPtr.Zero);
base.WndProc(ref msg);
e.Handled = true;
}

private void uiIntercept_KeyUp(object sender, KeyEventArgs e)
{
if(!Visible)
return;
if(e.Control || e.Alt)
return;
switch(e.KeyCode)
{
case Keys.Up:
case Keys.Down:
case Keys.PageUp:
case Keys.PageDown:
case Keys.Escape:
break;
default:
return;
}

Message msg = Message.Create(this.Handle, WM_KEYUP, new IntPtr((int)e.KeyCode), IntPtr.Zero);
base.WndProc(ref msg);
e.Handled = true;
}

private void uiIntercept_MouseWheel(object sender, MouseEventArgs e)
{
if(!Visible)
return;
if(Form.ModifierKeys == Keys.Control)
return;

Int32 wParam = 0;
wParam = e.Delta << 16;

Message msg = Message.Create(this.Handle, WM_MOUSEWHEEL, new IntPtr(wParam), IntPtr.Zero);
base.WndProc(ref msg);
}


To make sure the text box doesn't catch mouse wheel messages when the auto complete window is open, you need to make your own custom text box derived from either TextBox or RichText, and override WndProc as well as add an extra property, like this:


bool ignoreMouseWheel;
public bool IgnoreMouseWheel
{
get
{
return ignoreMouseWheel;
}
set
{
ignoreMouseWheel = value;
}
}

protected override void WndProc(ref Message m)
{
if(m.Msg == AutoCompleteBox.WM_MOUSEWHEEL && IgnoreMouseWheel)
OnMouseWheel(new MouseEventArgs(Form.MouseButtons, 0, Form.MousePosition.X, Form.MousePosition.Y, m.WParam.ToInt32() >> 16));
else
base.WndProc (ref m);
}


IgnoreMouseWheel is set to true when the auto complete box is visible, and false when it's not. You can use the VisibleChanged event like this:


private void AutoCompleteForm_VisibleChanged(object sender, System.EventArgs e)
{
BufferBox.IgnoreMouseWheel = this.Visible;
}


Hope this helps Big Grin

By the way, if someone finds out a better way, please do tell me.
GeneralRe: Using a form instead
SandeepRaj
22:23 4 May '05  
You are right it is really complicated.
I have found a reusable class for this purpose.
Please read this.
Hope this helps!

SR
GeneralRe: Using a form instead
reinux
22:40 4 May '05  
Awesome

Thanks!

Should help out in my current project too.
GeneralHow to fix the First Known Bug
goychev
1:34 14 Feb '05  
First - Thank you! Great job you've done!

And now the problem:
for the designer problem and "index out of bounds" you can add a check for items in the listbox before trying to get an item in the OnDrawItem of GListBox class
example follows:
if (Items.Count > 0 )
{
   item = (GListBoxItem) Items[e.Index];
}

and another issue: the designer of GListBox.cs complained that it could not load.
To fix: Just move the GListBox item to the end of the file and the error disapears.



Thanks,
igoychev
GeneralRe: How to fix the First Known Bug
adamvanner
4:13 2 Oct '07  
its also worth avoiding using try and catch in the way you have specified. logical errors like thhis one should never, in normal execution occur, so by catching it, you are effectively sweaping the error under the carpet. The method goychev suggests works fine


Last Updated 13 Jan 2004 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010