65.9K
CodeProject 正在变化。 阅读更多。
Home

类似 Intellisense 的方法选择弹出窗口

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.84/5 (13投票s)

2008 年 1 月 9 日

CPOL

6分钟阅读

viewsIcon

80261

类似 Intellisense 的方法选择弹出窗口

引言

如今,Intellisense 已成为各种不同应用程序中必不可少的功能。由于桌面上有大量未被利用的 CPU 资源,应用程序变得越来越智能,并找到了利用这些资源的方法。

Using the Code

我将在此解释的技术是一种用户友好的方式,可以在用户处理主要任务时提示或帮助他们进行下一步操作。例如:

  1. 用户输入文本,拼写检查器(在 WPF 中触手可及)会查找拼写错误的单词,应用程序可以向用户建议一个选择。
  2. 用户键入脚本,应用程序允许用户从可能性列表中选择下一个命令。
  3. 等等。

我们可以继续列举,但我希望您已经明白了。在我的例子中,我将向您展示如何帮助用户构建 LINQ 查询。

clip_image001

为了使解释简短且切题,我们来限定一下问题范围:应用程序将在用户键入预定义关键字并按“.”(点)时显示弹出窗口。该弹出窗口将包含可应用于该对象的可能方法的列表。

让我们创建一个类来提供项目方法的列表。假设项目类型为 SyndicationItem。因此,我们将使用反射方法(如果您不知道反射是什么,应该去了解一下,它在各种应用程序中都非常有用)来查找 SyndicationItem 类型的所有方法。您可以使用对象的 .GetType(),也可以使用简单的 typeof(...) 构造。以下代码片段正是这样做的:

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

现在,我们进入 Blend 来创建弹出窗口。从控件列表中选择一个 Popup 控件,并将其拖放到表单上的任意位置(最好将其放在将要绑定到的对象上,或者至少放在其父布局元素上)。您可以将任何单个控件放在弹出窗口上,但如果您希望以后扩展功能,请放置一个布局元素。在我的示例中,您可以看到我放置了一个 Grid 元素。将要填充我们方法的 ListBox 定位在 Grid 上。您必须在 Popup 上设置以下属性:

  1. Placement="Bottom" - 这会告诉系统将 Popup 定位在它将引用的元素的底部。这与 Visual Studio 在您按“.”的下方和右侧显示方法 intellisense 的方式相同。
  2. StaysOpen ="False" - 这会告诉 WPF 在弹出窗口失去焦点时关闭它。
  3. IsOpen="False" - 这会在表单初始化时隐藏弹出窗口。

要使 ListBox 正确显示方法名称,您必须将其绑定到我们之前定义的 ItemMethods 类。不要忘记编译代码 - 这将允许 Expression Blend 找到它并帮助您进行绑定。

  1. 转到 ItemsSource 属性的高级属性(属性值框右侧的小矩形),然后单击数据绑定。
  2. 从按下“+ CLR Object”按钮后出现的列表中添加 ItemMethods 对象。这将添加您的新数据源。
  3. 在字段树(右侧)上,选择 Methods 集合。

如果您做得一切正确,您将在 Popup 中看到 SyndicationItem 方法的列表。请注意,Expression Blend 已为您添加了一个新的数据源...

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

... 并为您的 ListBox 添加了一个绑定源:ItemsSource="{Binding Path=Methods, Mode=OneWay, Source={StaticResource ItemMethodsDS}}"

我正在准备一篇关于数据绑定的长文(它是 WPF 中一个庞大、强大且极其重要的部分,您现在应该了解它),但简而言之,这个特定的 string 告诉 WPF 从 ItemMethodsDS 数据源中的 Methods 属性获取项目。

此时,您可以随意自定义您的 ListBox。例如,我决定删除滚动条(HorizontalScrollBarVisibility="Hidden"VerticalScrollBarVisibility="Hidden"),并使 ListBox 可搜索(当您开始键入字母时,它会跳转到以这些字母开头的项目)。

   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>

为了在您按 Enter(选择所需项目后)或按 Escape 时使 Popup 消失,我们需要让 ListBox 处理这些事件。因此,您需要一个事件处理程序(请注意 XAML 上方的 KeyDown="OnMethodsSelectionKeyDown" 属性)。为此,您必须在 ListBox 的事件列表中的 KeyDown 事件中放置一个事件处理程序名称。按 Enter 后,您将被带回 Visual Studio 来编辑您的事件处理程序。它可能看起来像这样:

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

到目前为止,我们所做的只是控制 Popup 的行为和内容,但我们还没有做任何事情来触发它的出现。在此,您的想象力是无限的。为了使此示例起作用,我决定在用户在脚本编辑器中键入 item. 时显示 Popup。一旦他按下“.”,弹出窗口就会出现,允许他将选定的方法插入回脚本。以下 KeyUp 文本框事件处理程序的代码允许我这样做。请注意,Key.OemPeriod 值用于标识按下的“.”(点)。这对我不那么明显。另外,请注意硬编码的 item. 热词。这样做是为了简化解释。在您的代码中,应修改它以反映您的需求。

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

精确地将 Popup 放置在所需位置有点棘手 - 它具有几乎无限的各种放置组合选择(有关 Placement 属性,请参阅 MSDN 帮助。在我的情况下,我需要如上所述,在按下的“.”的右下方打开它。因此,我的 Popup 具有 Placement="Bottom" - 这将使其显示在“.”下方。

您会问:如何找到“.”的位置?这是一个很好的问题!

在 Windows Forms 中并不容易,但在 WPF 中却非常容易。可以通过调用 GetrectFromCharacterIndex 方法找到 TextBox 中的字符位置。但这将为您提供字符在 TextBox 内的坐标,并且 Popup 将在错误的位置打开,因为它将相对于其父 Layout 元素计算其位置。这并非我们所需要的。为了补偿计算,我们需要将 Popup PlacementTarget 指向我们的 TextBox(请参阅上面的代码:PlacementTarget = txtFilterText)。

现在我们完成了!启动您的脚本编辑器并开始键入。您应该会看到与我在本文开头发布的图片非常相似的东西。

关注点

  • Reflection(反射)
  • WPF

历史

  • 2008 年 1 月 9 日:首次发布

这是一系列文章中的一篇。敬请期待。

© . All rights reserved.