`

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
{
    /// <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>

 

分享到:
评论

相关推荐

    通用WPF主题控件包rubyer-wpf-master

    通用WPF主题控件包rubyer-wpf-master是一款专为Windows Presentation Foundation (WPF) 应用程序设计的开源UI框架。它提供了丰富的主题和控件,旨在帮助开发者快速构建美观且用户友好的应用程序界面。在2.0.0版本中...

    gong-wpf-dragdrop, GongSolutions.WPF.DragDrop 库是WPF的拖动'n'拖放框架.zip

    gong-wpf-dragdrop, GongSolutions.WPF.DragDrop 库是WPF的拖动'n'拖放框架 简介GongSolutions.WPF.DragDrop 库是一个易于使用的拖拉'n'拖放框架。特性使用 MVVM: 拖放逻辑可以放在ViewModel中。 代码不需要放在in中...

    C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手 4

    C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手 C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手 C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手

    WPF-Samples-master_WPF基本sample_

    WPF的基本空间历程,使用.net core3.0.1版本

    WPF-进阶视频教程(共113集)-032ComboBox控件.mp4

    WPF-进阶视频教程(共113集)-032ComboBox控件.mp4

    WPF-Diagram-Designer:WPF图表设计器源代码

    通过深入研究WPF-Diagram-Designer的源代码(如WPF-Diagram-Designer-master文件夹中的内容),开发者不仅可以学习到如何在WPF中构建复杂的图形界面,还可以了解到图形编辑器的设计原理和实现细节,对于提升图形应用...

    WPF combobox button 漂亮样式

    在Windows Presentation Foundation (WPF) 中,ComboBox是一个常用的控件,它结合了下拉列表和文本框的功能,允许用户从预定义的选项中选择或输入自定义值。本篇文章将详细探讨如何为WPF ComboBox创建漂亮的按钮样式...

    Wpf-glTF-testing.zip

    在“Wpf-glTF-testing.zip”压缩包中,我们有一个基于WPF(Windows Presentation Foundation)的简化glTF文件查看器项目。 WPF是.NET Framework的一部分,是一个用于构建Windows桌面应用程序的框架。它提供了丰富的...

    WPF-MaterialDesign-master.zip_WPF_WPF非常好的界面_包括多种漂亮的皮肤_漂亮的控件_配色

    在本项目"WPF-MaterialDesign-master.zip"中,重点在于利用**Material Design**这一设计语言来增强WPF应用的视觉效果。Material Design是Google推出的一种设计规范,其灵感来源于现实世界中的纸张和墨水,强调层次感...

    WPF--Effect特效

    标题中的"WPF--Effect特效"指的是Windows Presentation Foundation(WPF)中的Effect特效。WPF是.NET Framework的一个重要组件,用于构建丰富的桌面应用程序。Effect特效在WPF中是用来改变UI元素外观的一...

    wpf---StatusBar

    “wpf---StatusBar”这个标题表明我们将探讨的是WPF(Windows Presentation Foundation)框架中的StatusBar组件。WPF是.NET Framework的一部分,用于构建桌面应用程序,它提供了丰富的用户界面(UI)功能。StatusBar...

    WPF-强大的图表.zip

    **WPF - 强大的图表技术** Windows Presentation Foundation (WPF) 是Microsoft开发的一个用于构建桌面应用程序的框架,它提供了丰富的图形系统,包括对2D和3D图形的强大支持。在WPF中,开发人员可以利用各种图表...

    WPF 下拉列表框ComboBox扩展控件

    在本文中,我们将深入探讨如何在WPF(Windows Presentation Foundation)环境中扩展标准的ComboBox控件,使其能够包含更丰富的交互式元素,如DataGrid、ListView和ListBox。这将极大地提升用户体验,同时也为开发者...

    wpf-datagrid-access DB

    在这个“wpf-datagrid-access DB”主题中,我们将深入探讨如何利用WPF Datagrid与Microsoft Access数据库进行交互,实现数据的读取、更新和保存。 1. **WPF Datagrid简介** - Datagrid是WPF中的一个数据展示控件,...

    WPF combobox重写类(可获取高亮项)

    在Windows Presentation Foundation (WPF) 中,ComboBox是一个常用的控件,它结合了下拉列表和文本框的功能,允许用户从预定义的选项中选择或输入自定义值。然而,WPF的ComboBox默认并不提供直接获取高亮项(即当前...

    C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手1

    C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手 C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手 C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手

    WPF-ScreenData.zip

    超酷WPF数据可视化,全套源程序,非常适合做数据可视化的程序员使用,WPF,XAML。 找了好久的资源,附有很多很详细的插图,是大数据时代不可缺少的可视化学习数据,仅供大家学习使用请勿用做商业用途。与大家一起...

    wpf comboBox 下拉框全国地址联动

    在本文中,我们将深入探讨如何在WPF(Windows Presentation Foundation)应用程序中实现一个基于comboBox的全国地址联动功能。ComboBox是WPF中一个常用的控件,它允许用户在下拉列表中选择一个项或者输入自定义内容...

    Prism-Samples-Wpf-master11-15.zip

    Prism-Samples-Wpf-master11-15的VS2017版本实现,下载手动重新安装一下nuget包即可,方便大家学习

    practical-wpf-charts-graphics-master.rar

    该资源"practical-wpf-charts-graphics-master.rar"包含了这本书的源代码,为读者提供了丰富的实践案例和深入理解WPF图表及图形编程的宝贵材料。 WPF(Windows Presentation Foundation)是.NET Framework的一部分...

Global site tag (gtag.js) - Google Analytics