wpf - example to enhance ComboBox for AutoComplete

46 篇文章 0 订阅

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
{
    /// <summary>
    /// Focus Behavior, control shall be focused if the IsFocus dependency property is true
    /// </summary>
    public class ComboBehavior : Behavior<ComboBox>
    {
        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<DelayTask> _delayTasks = new List<DelayTask>();
        private readonly Delegate _textChangeHandler;

        private CancellationTokenSource _searchCancellationToken;
        private IList<SymbolVm> _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<TextBox>("PART_EditableTextBox");

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

            _SymbolSource = new List<SymbolVm>();

            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<SymbolVm>) 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<TextBox>("PART_EditableTextBox");
            // ...
        }
with the help of theVIsualTreeExtension, you can have find parent/child. the utility code is as follow.
public static T FindChild<T>(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<T>(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<TextBox>("PART_EditableTextBox");

            if (_editableTextBox != null)
            {
                _editableTextBox.MinWidth = 100;
            }
            // ...
        }
the xaml file for the combobox is as follow.
 
                <ComboBox
                    x:Name="cmbx"
                    Grid.Row="1" 
                    Grid.Column="0"
                    Style="{DynamicResource ComboBox}" 
                    Margin="4"
                    Text="{Binding SearchTxt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                    SelectedValue="{Binding SearchTxt}"
                    SelectedValuePath="Alias"
                    DisplayMemberPath="InstrDisplay"  
                    TextSearch.TextPath="Alias"
                    IsTextSearchEnabled="False"
                    IsEditable="True"
                    ToolTip="{Binding SymbolInputErrorMessage, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
                    >
                    <ComboBox.Resources>
                        <SolidColorBrush x:Key="{x:Static SystemColors.WindowBrushKey}" Color="LightGray" />
                    </ComboBox.Resources>
          <i:Interaction.Behaviors>
            <behaviors:ComboBehavior
                            SymbolService="{Binding SymbolService}"
              IsFocused="{Binding DisplaySymbolSelection, Mode=OneWay}" />
          </i:Interaction.Behaviors>
                </ComboBox>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值