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






3.84/5 (13投票s)
类似 Intellisense 的方法选择弹出窗口
引言
如今,Intellisense 已成为各种不同应用程序中必不可少的功能。由于桌面上有大量未被利用的 CPU 资源,应用程序变得越来越智能,并找到了利用这些资源的方法。
Using the Code
我将在此解释的技术是一种用户友好的方式,可以在用户处理主要任务时提示或帮助他们进行下一步操作。例如:
- 用户输入文本,拼写检查器(在 WPF 中触手可及)会查找拼写错误的单词,应用程序可以向用户建议一个选择。
- 用户键入脚本,应用程序允许用户从可能性列表中选择下一个命令。
- 等等。
我们可以继续列举,但我希望您已经明白了。在我的例子中,我将向您展示如何帮助用户构建 LINQ 查询。

为了使解释简短且切题,我们来限定一下问题范围:应用程序将在用户键入预定义关键字并按“.
”(点)时显示弹出窗口。该弹出窗口将包含可应用于该对象的可能方法的列表。
让我们创建一个类来提供项目方法的列表。假设项目类型为 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
上设置以下属性:
Placement="Bottom"
- 这会告诉系统将Popup
定位在它将引用的元素的底部。这与 Visual Studio 在您按“.
”的下方和右侧显示方法 intellisense 的方式相同。StaysOpen ="False"
- 这会告诉 WPF 在弹出窗口失去焦点时关闭它。IsOpen="False"
- 这会在表单初始化时隐藏弹出窗口。
要使 ListBox
正确显示方法名称,您必须将其绑定到我们之前定义的 ItemMethods
类。不要忘记编译代码 - 这将允许 Expression Blend 找到它并帮助您进行绑定。
- 转到
ItemsSource
属性的高级属性(属性值框右侧的小矩形),然后单击数据绑定。 - 从按下“+ CLR Object”按钮后出现的列表中添加
ItemMethods
对象。这将添加您的新数据源。 - 在字段树(右侧)上,选择
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 日:首次发布
这是一系列文章中的一篇。敬请期待。