Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Intellisense-like Method Selection Pop-up Window

0.00/5 (No votes)
9 Jan 2008 1  
Intellisense-like method selection pop-up window

Introduction

Today, intellisense becomes a must in a whole range of different applications. Applications become smarter due to this enormous unharvested CPU power that is idling on your desktop and the applications find a way to use it.

Using the Code

The technique that I'm going to explain here is a user-friendly way to hint or help the user with her or his next actions while she or he works on the primary task. For example:

  1. The user types a text, a spell checker (which is at your fingertips in WPF) finds the misspelled words and the application can suggest a choice to the user.
  2. The user types a script and the application lets the user choose a next command from a list of possibilities.
  3. Etc.

We can go on with the list, but, I hope, you got the idea. In my case, I'll show you how to help the user construct a LINQ query.

clip_image001

To keep the explanation short and to the point, let's scope the problem: the application will show a popup only when the user types a predefined keyword and presses a "." (dot). The popup will contain a list of possible methods that can be applied to the object.

Let's create a class that will provide a list of the item's methods. Let's say that the item is of type SyndicationItem. So we'll use reflection methods (if you don't know what reflection is, you should look into it, it's very useful in a whole range of applications) to find all the methods of the SyndicationItem type. You can use a .GetType() of an object or you can use a simple typeof(...) construct. The following code snippet does just this:

   1: public class ItemMethods
   2: {
   3:     public IEnumerable<string> Methods
   4:     {
   5:         get
   6:         {
   7:             Type type = typeof(SyndicationItem);
   8:  
   9:             var methods = (from method in 
  10:                           type.GetMethods(System.Reflection.BindingFlags.Public | 
  11:                           System.Reflection.BindingFlags.Instance)
  12:                           select method.Name).ToList<string>();
  13:  
  14:             return methods;
  15:         }
  16:     }
  17: }

Now let's go to Blend and create our popup. Select a Popup control from the controls list and drop it anywhere on the form (better to put it on the object that it'll be binded to or, at least, on its parent layout element). You can put any single control on the popup, but, if you'd like to extend your functionality later, put some layout element. In my example, you can see that I've put a Grid element. The ListBox that will be populated with our methods is positioned on the Grid. You have to set the following properties on the Popup:

  1. Placement="Bottom" - This tells the system to position the Popup at the bottom of the element it will be referring to. The same way as Visual Studio shows methods' intellisense below and to the right of the "." that you press.
  2. StaysOpen ="False" - This tells the WPF to close the Popup if it loses focus.
  3. IsOpen="False" - This hides the Popup on form initialization.

To make the ListBox display method names correctly, you have to bind it to the ItemMethods class that we've defined previously. Don't forget to compile your code - this will allow the Expression Blend to find it and help you with binding.

  1. Go to the Advanced Properties of the ItemsSource property (small rectangle to the right of the property value box) and click on Data Binding.
  2. Add the ItemMethods object from the list that will be presented to you after you press "+ CLR Object" button. This will add your new Data Source.
  3. On the Fields tree (on the right side), select the Methods collection.

If you did everything right - you'll see a list of SyndicationItem methods in the Popup. Note that Expression Blend has added a new data source for you...

   1: <ObjectDataProvider x:Key="ItemMethodsDS" 
   2:     d:IsDataSource="True" 
   3:     ObjectType="{x:Type Castile:ItemMethods}"/>

... and added a bind source to your ListBox: ItemsSource="{Binding Path=Methods, Mode=OneWay, Source={StaticResource ItemMethodsDS}}".

I'm preparing a big article about data binding (it's a huge, powerful and extremely important part of WPF that you should now), but in a nutshell, this specific string tells WPF to take the items from ItemMethodsDS data source from a property Methods.

At this point, you're free to customize your ListBox. For example, I've decided to remove the scrollbars (HorizontalScrollBarVisibility="Hidden" and VerticalScrollBarVisibility="Hidden") and make the ListBox searchable (when you start typing the letters, it'll jump to the item that starts with these letters).

   1: <Popup x:Name="popupLinqMethods" Height="Auto" Width="150" 
   2:        StaysOpen="False" Placement="Bottom" IsOpen="false" 
   3:        d:LayoutOverrides="Width, Margin" 
   4:        HorizontalAlignment="Left">
   5:     <Grid Width="Auto" Height="Auto">
   6:         <ListBox x:Name="lstMethodsSelection" 
   7:              ScrollViewer.HorizontalScrollBarVisibility="Hidden" 
   8:              ScrollViewer.VerticalScrollBarVisibility="Hidden" 
   9:              KeyDown="OnMethodsSelectionKeyDown" 
  10:              SelectedIndex="0" 
  11:              IsTextSearchEnabled="True" 
  12:              ItemsSource="{Binding Path=Methods, Mode=OneWay, 
                       Source={StaticResource ItemMethodsDS}}" 
  13:              ItemTemplate="{DynamicResource ListSyndicationObjectMethodsTemplate}"
  14:          />
  15:     </Grid>
  16: </Popup>

To make the Popup disappear when you press Enter (after selecting a desirable item) or when you press Escape, we need to let the ListBox handle these events. Hence you need an event handler (note the KeyDown="OnMethodsSelectionKeyDown" property in the XAML above). To do this, you have to put an event handler name in the KeyDown event on the event's list of the ListBox. After you'll press Enter, you'll be taken back to Visual Studio to edit your event handler. Here is how it can look like:

   1: private void OnMethodsSelectionKeyDown
            (object sender, System.Windows.Input.KeyEventArgs e)
   2: {
   3:     switch (e.Key)
   4:     {
   5:         case System.Windows.Input.Key.Enter:
   6:             // Hide the Popup
   7:             popupLinqMethods.IsOpen = false;
   8:  
   9:             ListBox lb = sender as ListBox;
  10:             if (lb == null)
  11:                 return;
  12:  
  13:             // Get the selected item value
  14:             string methodName = lb.SelectedItem.ToString();
  15:  
  16:             // Save the Caret position
  17:             int i = txtFilterText.CaretIndex;
  18:             
  19:             // Add text to the text
  20:             txtFilterText.Text = txtFilterText.Text.Insert(i, methodName);
  21:  
  22:             // Move the caret to the end of the added text
  23:             txtFilterText.CaretIndex = i + methodName.Length;
  24:  
  25:             // Move focus back to the text box. 
                  // This will auto-hide the PopUp due to StaysOpen="false"
  26:             txtFilterText.Focus();
  27:             break;
  28:  
  29:         case System.Windows.Input.Key.Escape:
  30:             // Hide the Popup
  31:             popupLinqMethods.IsOpen = false;
  32:             break;
  33:     }
  34: }

All what we did so far was the control of the behavior and the content of the Popup, but we did nothing to trigger its appearance. Here your imagination is your limit. To make this sample work, I've decided to show the Popup when the user will type the word item. in the script editor. As soon as he'll press "." - the Popup will appear, allowing him to insert the selected method back to the script. The following KeyUp text box event handler's code allows me to do just that. Note that the Key.OemPeriod value is used to identify the pressed "." (dot). It wasn't that obvious to me. Note, as well, the hardcoded item. hot-word. This is done to simplify the explanation. In your code, it should be modified to reflect your needs.

   1: private void OnFilterTextKeyUp(object sender, System.Windows.Input.KeyEventArgs e)
   2: {
   3:     TextBox txtBox = sender as TextBox;
   4:     if ((txtBox == null) || (txtBox.CaretIndex == 0))
   5:         return;
   6:  
   7:     // Check for a predefined hot-key
   8:     if (e.Key != System.Windows.Input.Key.OemPeriod)
   9:         return;
  10:  
  11:     // Get the last word in the text (preceding the ".")
  12:     string txt = txtBox.Text;
  13:     int wordStart = txt.LastIndexOf(' ', txtBox.CaretIndex - 1);
  14:     if (wordStart == -1)
  15:         wordStart = 0;
  16:  
  17:     string lastWord = txt.Substring(wordStart, txtBox.CaretIndex - wordStart);
  18:  
  19:     // Check if the last word equal to the one we're waiting
  20:     if (lastWord.Trim().ToLower() != "item.")
  21:         return;
  22:  
  23:     ShowMethodsPopup(txtBox.GetRectFromCharacterIndex(txtBox.CaretIndex, true));
  24: }
  25:  
  26: private void ShowMethodsPopup(Rect placementRect)
  27: {
  28:     popupLinqMethods.PlacementTarget = txtFilterText;
  29:     popupLinqMethods.PlacementRectangle = placementRect;
  30:     popupLinqMethods.IsOpen = true;
  31:     lstMethodsSelection.SelectedIndex = 0;
  32:     lstMethodsSelection.Focus();
  33: }

It's a little tricky to place the Popup exactly at the desired place - it has almost unlimited selection of different placement combinations (see MSDN help for the Placement property. In my case I needed, as I've mentioned above, to open it to the lower right from the pressed ".". So my Popup has the Placement="Bottom" - this will make it appear under the ".".

How to find the "." location - you'll ask? That's a great question!

It's not that easy in Windows Forms, but was super easy in WPF. The character location in a TextBox can be found by calling the GetrectFromCharacterIndex method. But this will give you the coordinates of the character inside the TextBox and the Popup will open in the incorrect place, because it'll calculate its location relative to its parent Layout element. This is not what we need. To compensate the calculation, we need to point the Popup PlacementTarget to our TextBox (see code above: PlacementTarget = txtFilterText).

Now we're done! Start your script editor and start typing. You should see something very similar to the picture I've posted at the beginning of this article.

Points of Interest

  • Reflection
  • WPF

History

  • 9th January, 2008: Initial post

This is a single article from a series. Stay tuned.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here