2014年4月24日木曜日

[WPF][C#]TextBox にインテリセンス機能追加

[C#][WPF]インテリセンスみたいなの作れるかな 」の記事を参考に、インテリセンス機能を自作してみました。
上記サイトでは ObjectDataProvider で候補を絞っていますが、データの再取得はしないので、今回は CollectionView で絞る方法で作りました。
一応、文字色も変えれるようにしています。カテゴリ別に色分けするとかで使えそうですね。


[XAML側]

        <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.ItemContainerStyle>
                    <Style TargetType="{x:Type ListBoxItem}">
                        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                        <Setter Property="FocusVisualStyle" Value="{x:Null}" />
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate>
                                    <StackPanel Orientation="Horizontal" 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="{StaticResource HoverBrush}" />
                            </Trigger>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Background" Value="{StaticResource SelectedBackgroundBrush}" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </ListBox.ItemContainerStyle>
            </ListBox>
        </Popup>







[Code側]

        /// <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 = "*";


            // 項目設定
            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>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void queryTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            try
            {
                // インテリセンス
                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(() =>
                {
                    try
                    {
                        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);
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message, TITLE, MessageBoxButton.OK, MessageBoxImage.Warning);
                    }
                }));
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, TITLE, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <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.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();
        }



TextBox のテンプレートをカスタムすれば、カスタムコントロール化も出来ますね。
Popup と ListBox をコードから生成させてテンプレート内の任意の位置に追加する感じで。



0 件のコメント:

コメントを投稿