IgorShare Thoughts and Ideas

Consulting and Training

Archive for January 9th, 2008

Intellisense-like method selection pop-up window

Posted by Igor Moochnick on 01/09/2008

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

The technique, I’m going to explain here, is a user-friendly way to hint or help user with their next actions while he works on his primary task. For example:

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

 

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

clip_image001

To make the explanation short and to the point, let’s scope the problem: the application will show a pop-up only when he 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 item’s methods. Let’s say that item is of type SyndicationItem. So we’ll use reflection methods (if you don’t know what reflections it – 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 pop-up. 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, to on its parent layout element). You can put any single control on the pop-up, 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 pop-up at the bottom of the element it’ll be referring to. The same way as VS 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 looses a focus
  3. IsOpen=”False” – hides the Popup on form initialization

To make the ListBox to display method names correctly you have to bind it to the ItemMethods class, 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 Bled 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 you should now), but in a nutshell this specific strings tell WPF to take the items from ItemMethodsDS data source from a proporty Methods.

At this point you’re free to customize your list box. For example I’ve decided to remove the scrollbars (HorizontalScrollBarVisibility=”Hidden” and  VerticalScrollBarVisibility=”Hidden”) and make the ListBox searchable (when you start type 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 to disappear when you press Enter (after selecting a desirable item) or when you press Escape, we need to let the ListBox to 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 the VS to edit your event handler. Here 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 it 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 it’s location relative to it’s 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 the post.

Posted in C#, Software, Tutorials, WPF | 5 Comments »