wpf - example to enhance ComboBox for AutoComplete

阅读更多

first let’s see an example of the code (the behavior code that turns a combobox to a source of autocomplete source)

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interactivity;
using log4net;

namespace UI.Behaviors
{
    /// 
    /// Focus Behavior, control shall be focused if the IsFocus dependency property is true
    /// 
    public class ComboBehavior : Behavior
    {
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        public static readonly DependencyProperty IsFocusedProperty = 
            DependencyProperty.Register(
            "IsFocused", 
            typeof(bool), 
            typeof(ComboBehavior), 
            new PropertyMetadata(default(bool), IsFocusedChanged));

        public static readonly DependencyProperty HasInitialFocusProperty = 
            DependencyProperty.Register(
            "HasInitialFocus", 
            typeof(bool), 
            typeof(ComboBehavior), 
            new PropertyMetadata(default(bool)));

        public static readonly DependencyProperty MaxSymbolInDropDownProperty =
            DependencyProperty.Register(
            "MaxSymbolInDropDown",
            typeof(int),
            typeof(ComboBehavior),
            new PropertyMetadata(20));

        public static readonly DependencyProperty SymbolServiceProperty =
            DependencyProperty.Register(
            "SymbolService",
            typeof(SymbolService),
            typeof(ComboBehavior),
            new PropertyMetadata(null));

        private readonly List _delayTasks = new List();
        private readonly Delegate _textChangeHandler;

        private CancellationTokenSource _searchCancellationToken;
        private IList _SymbolSource;
        private TextBox _editableTextBox;

        public ComboBehavior()
        {
            _searchCancellationToken = new CancellationTokenSource();
            _textChangeHandler = new TextChangedEventHandler(ComboBox_TextChanged);
        }

        public bool HasInitialFocus
        {
            get
            {
                return (bool)GetValue(HasInitialFocusProperty);
            }
            set
            {
                SetValue(HasInitialFocusProperty, value);
            }
        }

        public bool IsFocused
        {
            get
            {
                return (bool)GetValue(IsFocusedProperty);
            }
            set
            {
                SetValue(IsFocusedProperty, value);
            }
        }

        public SymbolService SymbolService
        {
            get
            {
                return (SymbolService)GetValue(SymbolServiceProperty);
            }

            set
            {
                SetValue(SymbolServiceProperty, value);
            } 
        }

        public int MaxSymbolInDropDown
        {
            get
            {
                return (int)GetValue(MaxSymbolInDropDownProperty);
            }
            set
            {
                SetValue(MaxSymbolInDropDownProperty, value);
            }
        }

        protected override void OnAttached()
        {
            AssociatedObject.Loaded += AssociatedObjectLoaded;
            AssociatedObject.DropDownOpened += AssociatedObject_DropDownOpened;
            AssociatedObject.AddHandler(TextBoxBase.TextChangedEvent, _textChangeHandler);
            base.OnAttached();
        }

        protected override void OnDetaching()
        {
            AssociatedObject.DropDownOpened -= AssociatedObject_DropDownOpened;

            if (_textChangeHandler != null)
            {
                AssociatedObject.RemoveHandler(TextBoxBase.TextChangedEvent, _textChangeHandler);
            }

            base.OnDetaching();
        }

        private void AssociatedObject_DropDownOpened(object sender, EventArgs e)
        {
            var combo = (ComboBox)sender;

            // prevent the inner text box from highlighting all after drop down opened.
            if (_editableTextBox != null && combo.SelectedItem == null)
            {
                _editableTextBox.Select(_editableTextBox.Text.Length, 0);
            }
        }

        private void ComboBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            var combo = (ComboBox)sender;

            var viewModel = combo.DataContext as DepthViewerViewModel;

            if (viewModel != null && !viewModel.IsSymbolComboValid)
            {
                viewModel.IsSymbolComboValid = true;
            }

            if (combo.SelectedItem == null)
            {
                var newCallCancellation = new CancellationTokenSource();
                Interlocked.Exchange(ref _searchCancellationToken, newCallCancellation).Cancel();

                new DelayTask(TimeSpan.FromMilliseconds(300), _delayTasks)
                     .Task.ContinueWith(
                         t => SearchSymbol(combo, combo.Text),
                         _searchCancellationToken.Token,
                         TaskContinuationOptions.OnlyOnRanToCompletion,
                         UITaskSchedulerService.Instance.GetUITaskScheduler())
                     .LogTaskExceptionIfAny(Log);
            }
        }

        private void SearchSymbol(ComboBox combo, string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                combo.ItemsSource = _SymbolSource.Take(MaxSymbolInDropDown);
            }
            else
            {
                combo.ItemsSource = _SymbolSource.Where(r => r.Alias.ToLower().Contains(key.ToLower())).OrderBy(r=>r.Alias)
                    .Concat(_SymbolSource.Where(r=>r.InstrDisplay.ToLower().Contains(key.ToLower())).OrderBy(r=>r.Alias))
                    .Distinct()
                    .Take(MaxSymbolInDropDown);
            }

            combo.IsDropDownOpen = true;
        }


        private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
        {
            AssociatedObject.Loaded -= AssociatedObjectLoaded;

            _editableTextBox = AssociatedObject.FindChild("PART_EditableTextBox");

            if (_editableTextBox != null)
            {
                _editableTextBox.MinWidth = 100;
            }

            _SymbolSource = new List();

            if (SymbolService != null)
            {
                SymbolService.SearchSymbols("").ForEach(i => _SymbolSource.Add(GetSymbolVm(i)));
            }

            AssociatedObject.ItemsSource = _SymbolSource.Take(MaxSymbolInDropDown);
            if (HasInitialFocus || IsFocused)
            {
                GotFocus();
            }
        }

        private SymbolVm GetSymbolVm(Symbol inst)
        {
            return new SymbolVm
                {
                    Alias = inst.Alias,
                    InstrDisplay = string.Format("{0, -12}\t {1,-30}", inst.Alias, inst.Description),
                };
        }

        private static void IsFocusedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            if ((bool)args.NewValue)
            {
                ((ComboBehavior)sender).GotFocus();
            }
            else
            {
                ((ComboBehavior)sender).ClearFocus();
            }
        }

        private void GotFocus()
        {
            new DelayTask(TimeSpan.FromMilliseconds(300), _delayTasks).Task.ContinueWith(
              t => AssociatedObject.Focus(),
              CancellationToken.None,
              TaskContinuationOptions.OnlyOnRanToCompletion,
              UITaskSchedulerService.Instance.GetUITaskScheduler());
        }

        private void ClearFocus()
        {
            AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
        }
    }
}
Basically what it does is to create an internal datasource (List) and turn that dynamically hijack the TextBox ItemsSource.
There are some thing that worth noticing now.
the default Combobox has issues that when you type first time, the text is highlighted. and this is due to some Internal problem that when the combobox ‘s drop down is opened. the Text is highlighted, we can fix that by the following code.
        private void AssociatedObject_DropDownOpened(object sender, EventArgs e)
        {
            var combo = (ComboBox)sender;

            // prevent the inner text box from highlighting all after drop down opened.
            if (_editableTextBox != null && combo.SelectedItem == null)
            {
                _editableTextBox.Select(_editableTextBox.Text.Length, 0);
            }
        }
you may wonder where the control _editableTextBoxis coming from:
        private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
        {
            AssociatedObject.Loaded -= AssociatedObjectLoaded;

            _editableTextBox = AssociatedObject.FindChild("PART_EditableTextBox");
            // ...
        }
with the help of theVIsualTreeExtension, you can have find parent/child. the utility code is as follow.
public static T FindChild(this DependencyObject parent, string childName) where T : DependencyObject
    {
      if (parent == null)
        return default (T);
      T obj = default (T);
      int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
      for (int childIndex = 0; childIndex < childrenCount; ++childIndex)
      {
        DependencyObject child = VisualTreeHelper.GetChild(parent, childIndex);
        if ((object) (child as T) == null)
        {
          obj = VisualTreeExtensions.FindChild(child, childName);
          if ((object) obj != null)
            break;
        }
        else if (!string.IsNullOrEmpty(childName))
        {
          FrameworkElement frameworkElement = child as FrameworkElement;
          if (frameworkElement != null && frameworkElement.Name == childName)
          {
            obj = (T) child;
            break;
          }
        }
        else
        {
          obj = (T) child;
          break;
        }
      }
      return obj;
    }
there are also some problems with width (the ComboBox’s TextBox control has MinWidth issue), which can be resolved by the following hack.
        private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
        {
            _editableTextBox = AssociatedObject.FindChild("PART_EditableTextBox");

            if (_editableTextBox != null)
            {
                _editableTextBox.MinWidth = 100;
            }
            // ...
        }
the xaml file for the combobox is as follow.
 
                
                    
                        
                    
          
            
          
                

 

你可能感兴趣的:(wpf,c#)