こちらのサイトでWPFでIntelliSenseを使う方法が例示されているが、いくつか不満点があるため何箇所か改良を行いました。
- フィルターでToUpperしていなかったので、すべての要素が正しく表示されない
→ToUpperを挿入した - 表示されている要素の幅次第でポップアップの幅も変わる(次図)
→ItemsPanelTemplateを用いることで解決した
変更後
<Window x:Class="IntelliSenseTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:IntelliSenseTest"
Title="MainWindow" Height="300" Width="500">
<Window.DataContext>
<local:ViewModel x:Name="vm"/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="local:IntelliSenseTextBox">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="TextBackground" Value="Ivory" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox AcceptsReturn="True" AcceptsTab="True" Name="queryTextBox" Margin="5"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
PreviewKeyDown="queryTextBox_PreviewKeyDown"/>
<Popup Name="popup" IsOpen="False" StaysOpen="False" MaxHeight="300" FocusVisualStyle="{x:Null}">
<ListBox IsTextSearchEnabled="False" Name="intellisenseListBox"
PreviewKeyDown="intellisenseListBox_PreviewKeyDown"
MouseDoubleClick="intellisenseListBox_MouseDoubleClick"
DisplayMemberPath="Key" SelectedValuePath="Key"
ItemsSource="{Binding}" FocusVisualStyle="{x:Null}"
>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Background="{TemplateBinding Background}">
<TextBlock Text="{Binding Key}" Foreground="{Binding Path=ForeColor}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Aqua" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="AliceBlue" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Popup>
</Grid>
</Window>
namespace IntelliSenseTest {
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
// 項目設定
var items = typeof(Brushes).GetProperties().Select(brush => new CandidateKeyItem() { Key = brush.Name, ForeColor = (Brush)brush.GetValue(null, null) }).ToList();
intellisenseListBox.DataContext = items;
this.intellisenseView = CollectionViewSource.GetDefaultView(items);
this.intellisenseView.Filter = filterIntellisense;
}
/// <summary>
/// 候補キーアイテム
/// </summary>
public class CandidateKeyItem {
/// <summary>
/// 候補キー
/// </summary>
public string Key { get; set; }
/// <summary>
/// 文字色
/// </summary>
public Brush ForeColor { get; set; }
}
/// <summary>
/// インテリセンスコレクションビュー
/// </summary>
private ICollectionView intellisenseView;
/// <summary>
/// カレント単語
/// </summary>
private string currentWord = "*";
/// <summary>
/// クエリテキストボックスプレビューキーダウンイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void queryTextBox_PreviewKeyDown(object sender, KeyEventArgs e) {
// インテリセンス
if (e.Key == Key.Back || e.Key == Key.Escape) {
popup.IsOpen = false;
return;
}
if (popup.IsOpen) {
if (e.Key == Key.Tab || e.Key == Key.Down) {
intellisenseListBox.Focus();
return;
}
if (e.Key == Key.Enter && intellisenseListBox.Items.Count != 0) {
if (intellisenseListBox.SelectedIndex == -1) {
intellisenseListBox.SelectedIndex = 0;
}
selectWord();
e.Handled = true;
return;
}
}
// フィルタ対象外判定
int key = (int)e.Key;
if ((key < 34) || (85 < key) && (key < 140) || (154 < key)) {
return;
}
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => {
currentWord = getCurrentWord(queryTextBox);
if (string.IsNullOrEmpty(currentWord)) {
popup.IsOpen = false;
return;
}
intellisenseView.Refresh();
// Popupを現在のキャレットのある位置へ表示
popup.PlacementTarget = queryTextBox;
popup.PlacementRectangle =
queryTextBox.GetRectFromCharacterIndex(queryTextBox.CaretIndex);
// 候補が0個の時は表示しない
popup.IsOpen = intellisenseListBox.Items.Count != 0;
if (popup.IsOpen && intellisenseListBox.SelectedItem != null) {
// 既に選択したものがあればそこにスクロール
intellisenseListBox.ScrollIntoView(intellisenseListBox.SelectedItem);
}
}));
}
/// <summary>
/// リストボックスプレビューキーダウンイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void intellisenseListBox_PreviewKeyDown(object sender, KeyEventArgs e) {
// ESCAPE押下でポップアップキャンセル
if (e.Key == Key.Escape) {
popup.IsOpen = false;
queryTextBox.Focus();
return;
}
// 文字選択判定
if (e.Key == Key.Enter || e.Key == Key.Tab || e.Key == Key.Space) {
selectWord();
e.Handled = true;
}
}
/// <summary>
/// リストビューダブルクリックイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void intellisenseListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
selectWord();
}
/// <summary>
/// カレント文字取得
/// </summary>
/// <param name="textBox"></param>
/// <returns></returns>
private string getCurrentWord(TextBox textBox) {
if (string.IsNullOrEmpty(textBox.Text)) {
return string.Empty;
}
if (textBox.CaretIndex == 0) {
return string.Empty;
}
int index = textBox.CaretIndex - 1;
int last = textBox.Text.LastIndexOfAny(new[] { ' ', '\r', '\n', '\t' }, index) + 1;
return textBox.Text.Substring(last, textBox.CaretIndex - last).ToUpper();
}
/// <summary>
/// インテリセンスフィルター
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private bool filterIntellisense(object args) {
bool result = false;
var word = args as CandidateKeyItem;
if (word != null) {
result = word.Key.ToUpper().Contains(currentWord);
}
return result;
}
/// <summary>
/// ワード選択
/// </summary>
private void selectWord() {
if (intellisenseListBox.SelectedValue == null) {
return;
}
var caretIndex = queryTextBox.CaretIndex;
var selectedText = intellisenseListBox.SelectedValue as string;
var topIndex = caretIndex - currentWord.Length;
// 選択されたものを挿入
var tmpText = queryTextBox.Text.Remove(topIndex, currentWord.Length);
queryTextBox.Text = tmpText.Insert(topIndex, selectedText);
queryTextBox.CaretIndex = topIndex + selectedText.Length;
popup.IsOpen = false;
queryTextBox.Focus();
}
}
}

