|
@@ -0,0 +1,391 @@
|
|
|
+/*-------------------------------------------------------------------------
|
|
|
+ * 功能描述:SCombobox
|
|
|
+ * 作者:xulisong
|
|
|
+ * 创建时间: 2019/6/10 11:28:37
|
|
|
+ * 版本号:v1.0
|
|
|
+ * -------------------------------------------------------------------------*/
|
|
|
+
|
|
|
+using System;
|
|
|
+using System.Collections;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.ComponentModel;
|
|
|
+using System.Linq;
|
|
|
+using System.Text;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using System.Windows;
|
|
|
+using System.Windows.Controls;
|
|
|
+using System.Windows.Controls.Primitives;
|
|
|
+using System.Windows.Data;
|
|
|
+using System.Windows.Input;
|
|
|
+using SAGA.DotNetUtils.WPF.Extend;
|
|
|
+
|
|
|
+namespace SAGA.DotNetUtils.WPF
|
|
|
+{
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 带搜索功能的comboBox
|
|
|
+ /// </summary>
|
|
|
+ /// [TemplatePart(Name = "PART_textBoxNewItem", Type=typeof(TextBox))]
|
|
|
+ [TemplatePart(Name = "PART_SearchText", Type = typeof(TextBox))]
|
|
|
+ [TemplatePart(Name = "PART_Popup", Type = typeof(Popup))]
|
|
|
+ [TemplatePart(Name = "PART_Button", Type = typeof(Button))]
|
|
|
+ public class SComboBox : ListBox
|
|
|
+ {
|
|
|
+ static SComboBox()
|
|
|
+ {
|
|
|
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(SComboBox), new FrameworkPropertyMetadata(typeof(SComboBox)));
|
|
|
+ }
|
|
|
+ public SComboBox()
|
|
|
+ {
|
|
|
+ Keyboard.AddPreviewKeyDownHandler(this, OnKeyDown);
|
|
|
+ Keyboard.AddPreviewKeyUpHandler(this, OnKeyUp);
|
|
|
+ this.AddHandler(ListBoxItem.PreviewMouseDownEvent, new MouseButtonEventHandler(MouseButtonEventHandler));
|
|
|
+ this.IsTextSearchCaseSensitive = false;
|
|
|
+ this.IsTextSearchEnabled = false;
|
|
|
+ this.InEdit = false;
|
|
|
+ this.SetValue(KeyboardNavigation.DirectionalNavigationProperty, KeyboardNavigationMode.Contained);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void MouseButtonEventHandler(object sender, MouseButtonEventArgs e)
|
|
|
+ {
|
|
|
+ var element = e.OriginalSource as UIElement;
|
|
|
+ if (element == null)
|
|
|
+ return;
|
|
|
+ var listItem = element.GetParentTypeSelf<ListBoxItem>();
|
|
|
+ if (listItem == null)
|
|
|
+ return;
|
|
|
+ if (listItem.DataContext == this.SelectedItem)
|
|
|
+ {
|
|
|
+ SyncSelectedItemText();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnKeyDown(object sender, KeyEventArgs e)
|
|
|
+ {
|
|
|
+ if (e.Key == Key.Enter && IsDropDownOpen)
|
|
|
+ {
|
|
|
+ this.IsDropDownOpen = false;
|
|
|
+ if (this.SelectedItem != null)
|
|
|
+ {
|
|
|
+ SyncSelectedItemText();
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!(Key.Up == e.Key || Key.Down == e.Key))
|
|
|
+
|
|
|
+ {
|
|
|
+ //EditableTextBox.Focus();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ UIElement item = ItemContainerGenerator.ContainerFromItem(SelectedItem) as UIElement;
|
|
|
+ if ((item == null) && (Items.Count > 0))
|
|
|
+ item = ItemContainerGenerator.ContainerFromItem(Items[0]) as UIElement;
|
|
|
+ if (item != null)
|
|
|
+ item.Focus();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private void OnKeyUp(object sender, KeyEventArgs e)
|
|
|
+ {
|
|
|
+ EditableTextBox.Focus();
|
|
|
+ }
|
|
|
+ #region 属性相关
|
|
|
+ /// <summary>
|
|
|
+ /// 关联文本
|
|
|
+ /// </summary>
|
|
|
+ public static readonly DependencyProperty TextProperty =
|
|
|
+ DependencyProperty.Register("Text", typeof(string), typeof(SComboBox));
|
|
|
+ /// <summary>
|
|
|
+ /// 关联文本
|
|
|
+ /// </summary>
|
|
|
+ public string Text
|
|
|
+ {
|
|
|
+ get { return (string)GetValue(TextProperty); }
|
|
|
+ set { SetValue(TextProperty, value); }
|
|
|
+ }
|
|
|
+ /// <summary>
|
|
|
+ /// 是否下拉打开
|
|
|
+ /// </summary>
|
|
|
+ public static readonly DependencyProperty IsDropDownOpenProperty =
|
|
|
+ DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(SComboBox));
|
|
|
+ /// <summary>
|
|
|
+ /// 是否下拉打开
|
|
|
+ /// </summary>
|
|
|
+ public bool IsDropDownOpen
|
|
|
+ {
|
|
|
+ get { return (bool)GetValue(IsDropDownOpenProperty); }
|
|
|
+ set { SetValue(IsDropDownOpenProperty, value); }
|
|
|
+ }
|
|
|
+ /// <summary>
|
|
|
+ /// Dependency backing for the MaxDropDownHeight property.
|
|
|
+ /// </summary>
|
|
|
+ public static readonly DependencyProperty MaxDropDownHeightProperty =
|
|
|
+ ComboBox.MaxDropDownHeightProperty.AddOwner(typeof(SComboBox));
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating the maximium height of the drop down.
|
|
|
+ /// </summary>
|
|
|
+ public double MaxDropDownHeight
|
|
|
+ {
|
|
|
+ get { return (double)GetValue(MaxDropDownHeightProperty); }
|
|
|
+ set { SetValue(MaxDropDownHeightProperty, value); }
|
|
|
+ }
|
|
|
+ /// <summary>
|
|
|
+ /// 是否下拉打开
|
|
|
+ /// </summary>
|
|
|
+ public static readonly DependencyProperty IsReadOnlyProperty =
|
|
|
+ DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(SComboBox));
|
|
|
+ /// <summary>
|
|
|
+ /// 是否下拉打开
|
|
|
+ /// </summary>
|
|
|
+ public bool IsReadOnly
|
|
|
+ {
|
|
|
+ get { return (bool)GetValue(IsReadOnlyProperty); }
|
|
|
+ set { SetValue(IsReadOnlyProperty, value); }
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+ /*
|
|
|
+ * 选中项会在数据源发生变化时变化,所以要保证全局的选中项
|
|
|
+ */
|
|
|
+ /// <summary>
|
|
|
+ /// 真实的选中项
|
|
|
+ /// </summary>
|
|
|
+ private object RealSelectedItem { get; set; }
|
|
|
+ #region 内部编辑
|
|
|
+ private bool InEdit { get; set; }
|
|
|
+ public void BeginInEdit()
|
|
|
+ {
|
|
|
+ InEdit = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void EndInEdit()
|
|
|
+ {
|
|
|
+ InEdit = false;
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+ #region 相关
|
|
|
+ /*
|
|
|
+ * 1、过滤过程中,对SelectedItem赋值,不要引起Text的变化
|
|
|
+ * 2、内部引发的Text事件,不触发过滤事件
|
|
|
+ * 3、编辑完成是离开控件
|
|
|
+ */
|
|
|
+ #endregion
|
|
|
+ private Popup Popup { get; set; }
|
|
|
+ protected TextBox EditableTextBox { get; set; }
|
|
|
+ public override void OnApplyTemplate()
|
|
|
+ {
|
|
|
+ base.OnApplyTemplate();
|
|
|
+ Popup = (Popup)this.Template.FindName("PART_Popup", this);
|
|
|
+ EditableTextBox = (TextBox)this.Template.FindName("PART_SearchText", this);
|
|
|
+ this.EditableTextBox.TextChanged += EditableTextBox_TextChanged;
|
|
|
+ this.EditableTextBox.LostFocus += EditableTextBox_LostFocus;
|
|
|
+
|
|
|
+ var toggle = (Button)this.Template.FindName("PART_Button", this);
|
|
|
+ toggle.Click += Toggle_Click;
|
|
|
+ this.PreviewMouseLeftButtonDown += Toggle_PreviewMouseLeftButtonDown;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Toggle_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
|
+ {
|
|
|
+ m_IsOpen = this.IsDropDownOpen;
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool m_IsOpen;
|
|
|
+ private void Toggle_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ if (this.ItemsSource == null)
|
|
|
+ return;
|
|
|
+ GlobalFlag = true;
|
|
|
+ RefreshFilter();
|
|
|
+ SetSelectedItem();
|
|
|
+ this.IsDropDownOpen = !m_IsOpen;
|
|
|
+ //Popup.StaysOpen = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static readonly DependencyProperty m_DisplayProperty =
|
|
|
+ DependencyProperty.Register("m_DisplayProperty", typeof(object), typeof(SComboBox));
|
|
|
+ private void EditableTextBox_LostFocus(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ GlobalFlag = true;
|
|
|
+ RefreshFilter();
|
|
|
+ SetSelectedItem();
|
|
|
+ }
|
|
|
+ private string GetDisplay( object item)
|
|
|
+ {
|
|
|
+ BindingOperations.SetBinding(this, m_DisplayProperty, new Binding(DisplayMemberPath) { Source = item });
|
|
|
+ var propertyValue = GetValue(m_DisplayProperty);
|
|
|
+ BindingOperations.ClearBinding(this, m_DisplayProperty);
|
|
|
+ return propertyValue?.ToString()??string.Empty;
|
|
|
+ }
|
|
|
+ private void SetSelectedItem()
|
|
|
+ {
|
|
|
+ if (this.RealSelectedItem == null)
|
|
|
+ {
|
|
|
+ if (this.ItemsSource != null)
|
|
|
+ {
|
|
|
+ foreach (var item in ItemsSource)
|
|
|
+ {
|
|
|
+ this.RealSelectedItem = item;//.FirstOrDefault();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ this.SelectedItem = this.RealSelectedItem;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ BeginInEdit();
|
|
|
+ this.Text = GetDisplay(this.SelectedItem);
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ EndInEdit();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private void EditableTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
|
|
+ {
|
|
|
+ TextChanged();
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnSelectionChanged(SelectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ BeginInEdit();
|
|
|
+ base.OnSelectionChanged(e);
|
|
|
+ if (!InSelected)
|
|
|
+ {
|
|
|
+ this.Text = GetDisplay(this.SelectedItem);
|
|
|
+ this.EditableTextBox.Select(this.Text.Length, 0);
|
|
|
+ this.RealSelectedItem = this.SelectedItem;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ EndInEdit();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 同步text显示
|
|
|
+ /// </summary>
|
|
|
+ private void SyncSelectedItemText()
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ BeginInEdit();
|
|
|
+ if (!InSelected)
|
|
|
+ {
|
|
|
+ var text = GetDisplay(this.SelectedItem);
|
|
|
+ //this.Text = this.SelectedItem?.ToString() ?? string.Empty;
|
|
|
+ this.EditableTextBox.Text = text;
|
|
|
+ this.EditableTextBox.Select(text.Length, 0);
|
|
|
+ this.RealSelectedItem = this.SelectedItem;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ EndInEdit();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ #region 选项控制
|
|
|
+ /// <summary>
|
|
|
+ /// 是否是内部选择
|
|
|
+ /// </summary>
|
|
|
+ private bool InSelected { get; set; } = false;
|
|
|
+ private void SetSelectedItem(object item)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ InSelected = true;
|
|
|
+ this.SelectedItem = item;
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ InSelected = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+ #region 过滤相关
|
|
|
+ private void RefreshFilter()
|
|
|
+ {
|
|
|
+ if (this.ItemsSource != null)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ BeginInEdit();
|
|
|
+ InSelected = true;
|
|
|
+ ICollectionView view = CollectionViewSource.GetDefaultView(this.ItemsSource);
|
|
|
+ view.Refresh();
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ InSelected = false;
|
|
|
+ EndInEdit();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /// <summary>
|
|
|
+ /// 全局过滤标志,为true 则不进行过滤,初始值为ture
|
|
|
+ /// </summary>
|
|
|
+ private bool GlobalFlag { get; set; } = true;
|
|
|
+ private bool FilterPredicate(object value)
|
|
|
+ {
|
|
|
+ if (GlobalFlag)
|
|
|
+ return true;
|
|
|
+ if (value == null)
|
|
|
+ return false;
|
|
|
+ var text = this.EditableTextBox.Text;
|
|
|
+ if (text.Length == 0)
|
|
|
+ return true;
|
|
|
+ string prefix = text;
|
|
|
+ return GetDisplay(value).Contains(prefix);
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
|
|
|
+ {
|
|
|
+ if (newValue != null)
|
|
|
+ {
|
|
|
+ ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
|
|
|
+ view.Filter += this.FilterPredicate;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (oldValue != null)
|
|
|
+ {
|
|
|
+ ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
|
|
|
+ view.Filter -= this.FilterPredicate;
|
|
|
+ }
|
|
|
+ base.OnItemsSourceChanged(oldValue, newValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void TextChanged()
|
|
|
+ {
|
|
|
+ if (!this.IsTextSearchEnabled && !this.InEdit)
|
|
|
+ {
|
|
|
+ var text = this.EditableTextBox.Text ?? string.Empty;
|
|
|
+ if (true || text.Length > 0)
|
|
|
+ {
|
|
|
+ GlobalFlag = false;
|
|
|
+ this.RefreshFilter();
|
|
|
+ //Poupup.IsOpen = true;
|
|
|
+ IsDropDownOpen = true;
|
|
|
+ foreach (object item in CollectionViewSource.GetDefaultView(this.ItemsSource))
|
|
|
+ {
|
|
|
+ string display = item.ToString();
|
|
|
+ SetSelectedItem(item);
|
|
|
+ if (display == text)
|
|
|
+ {
|
|
|
+ this.RealSelectedItem = item;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+}
|