ComboBox.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Controls.Primitives;
  6. using System.Windows.Data;
  7. using System.Windows.Input;
  8. using System.Windows.Media;
  9. #if NET40
  10. using System.Windows.Threading;
  11. #endif
  12. using HandyControl.Data;
  13. using HandyControl.Interactivity;
  14. namespace HandyControl.Controls;
  15. [TemplatePart(Name = AutoCompletePanel, Type = typeof(Panel))]
  16. [TemplatePart(Name = EditableTextBox, Type = typeof(System.Windows.Controls.TextBox))]
  17. [TemplatePart(Name = AutoPopupAutoComplete, Type = typeof(Popup))]
  18. public class ComboBox : System.Windows.Controls.ComboBox
  19. {
  20. #if NET40
  21. private string _searchText;
  22. private DispatcherTimer _autoCompleteTimer;
  23. #endif
  24. private bool _isAutoCompleteAction = true;
  25. private Panel _autoCompletePanel;
  26. private System.Windows.Controls.TextBox _editableTextBox;
  27. private Popup _autoPopupAutoComplete;
  28. private const string AutoCompletePanel = "PART_AutoCompletePanel";
  29. private const string AutoPopupAutoComplete = "PART_Popup_AutoComplete";
  30. private const string EditableTextBox = "PART_EditableTextBox";
  31. public ComboBox()
  32. {
  33. CommandBindings.Add(new CommandBinding(ControlCommands.Clear, (s, e) =>
  34. {
  35. if (IsReadOnly)
  36. {
  37. return;
  38. }
  39. SetCurrentValue(SelectedValueProperty, null);
  40. SetCurrentValue(SelectedItemProperty, null);
  41. SetCurrentValue(SelectedIndexProperty, -1);
  42. SetCurrentValue(TextProperty, "");
  43. }));
  44. }
  45. public override void OnApplyTemplate()
  46. {
  47. if (_editableTextBox != null)
  48. {
  49. BindingOperations.ClearBinding(_editableTextBox, System.Windows.Controls.TextBox.TextProperty);
  50. _editableTextBox.GotFocus -= EditableTextBox_GotFocus;
  51. _editableTextBox.LostFocus -= EditableTextBox_LostFocus;
  52. }
  53. base.OnApplyTemplate();
  54. if (IsEditable)
  55. {
  56. _editableTextBox = GetTemplateChild(EditableTextBox) as System.Windows.Controls.TextBox;
  57. if (_editableTextBox != null)
  58. {
  59. _editableTextBox.SetBinding(SelectionBrushProperty, new Binding(SelectionBrushProperty.Name) { Source = this });
  60. #if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472)
  61. _editableTextBox.SetBinding(SelectionTextBrushProperty, new Binding(SelectionTextBrushProperty.Name) { Source = this });
  62. #endif
  63. _editableTextBox.SetBinding(SelectionOpacityProperty, new Binding(SelectionOpacityProperty.Name) { Source = this });
  64. _editableTextBox.SetBinding(CaretBrushProperty, new Binding(CaretBrushProperty.Name) { Source = this });
  65. if (AutoComplete)
  66. {
  67. #if NET40
  68. _autoCompleteTimer = new DispatcherTimer
  69. {
  70. Interval = TimeSpan.FromMilliseconds(500)
  71. };
  72. _autoCompleteTimer.Tick += AutoCompleteTimer_Tick;
  73. #endif
  74. _autoPopupAutoComplete = GetTemplateChild(AutoPopupAutoComplete) as Popup;
  75. _autoCompletePanel = GetTemplateChild(AutoCompletePanel) as Panel;
  76. _editableTextBox.SetBinding(System.Windows.Controls.TextBox.TextProperty, new Binding(SearchTextProperty.Name)
  77. {
  78. UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
  79. Mode = BindingMode.OneWayToSource,
  80. #if !NET40
  81. Delay = 500,
  82. #endif
  83. Source = this
  84. });
  85. _editableTextBox.GotFocus += EditableTextBox_GotFocus;
  86. _editableTextBox.LostFocus += EditableTextBox_LostFocus;
  87. }
  88. }
  89. }
  90. }
  91. #if NET40
  92. private void AutoCompleteTimer_Tick(object sender, EventArgs e)
  93. {
  94. UpdateSearchItems(_searchText);
  95. _autoCompleteTimer.Stop();
  96. }
  97. #endif
  98. private void EditableTextBox_LostFocus(object sender, RoutedEventArgs e)
  99. {
  100. if (_autoPopupAutoComplete != null)
  101. {
  102. _autoPopupAutoComplete.IsOpen = false;
  103. }
  104. }
  105. #if !NET40
  106. protected override void OnDropDownClosed(EventArgs e)
  107. {
  108. base.OnDropDownClosed(e);
  109. _isAutoCompleteAction = false;
  110. }
  111. #endif
  112. private void EditableTextBox_GotFocus(object sender, RoutedEventArgs e)
  113. {
  114. if (_autoPopupAutoComplete != null && _editableTextBox != null &&
  115. !string.IsNullOrEmpty(_editableTextBox.Text))
  116. {
  117. _autoPopupAutoComplete.IsOpen = true;
  118. }
  119. }
  120. protected override void OnSelectionChanged(SelectionChangedEventArgs e)
  121. {
  122. _isAutoCompleteAction = false;
  123. base.OnSelectionChanged(e);
  124. #if NET40
  125. _isAutoCompleteAction = true;
  126. #endif
  127. }
  128. /// <summary>
  129. /// 数据搜索委托
  130. /// </summary>
  131. public Func<ItemCollection, object, IEnumerable<object>> SearchFunc { get; set; }
  132. /// <summary>
  133. /// 是否自动完成输入
  134. /// </summary>
  135. public static readonly DependencyProperty AutoCompleteProperty = DependencyProperty.Register(
  136. nameof(AutoComplete), typeof(bool), typeof(ComboBox), new PropertyMetadata(ValueBoxes.FalseBox, OnAutoCompleteChanged));
  137. private static void OnAutoCompleteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  138. {
  139. var ctl = (ComboBox) d;
  140. if (ctl._editableTextBox != null)
  141. {
  142. ctl.UpdateSearchItems(ctl._editableTextBox.Text);
  143. }
  144. }
  145. /// <summary>
  146. /// 是否自动完成输入
  147. /// </summary>
  148. public bool AutoComplete
  149. {
  150. get => (bool) GetValue(AutoCompleteProperty);
  151. set => SetValue(AutoCompleteProperty, ValueBoxes.BooleanBox(value));
  152. }
  153. /// <summary>
  154. /// 搜索文本
  155. /// </summary>
  156. internal static readonly DependencyProperty SearchTextProperty = DependencyProperty.Register(
  157. nameof(SearchText), typeof(string), typeof(ComboBox), new PropertyMetadata(default(string), OnSearchTextChanged));
  158. private static void OnSearchTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  159. {
  160. var ctl = (ComboBox) d;
  161. if (ctl._isAutoCompleteAction)
  162. {
  163. #if NET40
  164. ctl._searchText = e.NewValue as string;
  165. ctl._autoCompleteTimer.Stop();
  166. ctl._autoCompleteTimer.Start();
  167. #else
  168. ctl.UpdateSearchItems(e.NewValue as string);
  169. #endif
  170. }
  171. ctl._isAutoCompleteAction = true;
  172. }
  173. /// <summary>
  174. /// 搜索文本
  175. /// </summary>
  176. internal string SearchText
  177. {
  178. get => (string) GetValue(SearchTextProperty);
  179. set => SetValue(SearchTextProperty, value);
  180. }
  181. public static readonly DependencyProperty SelectionBrushProperty =
  182. TextBoxBase.SelectionBrushProperty.AddOwner(typeof(ComboBox));
  183. public Brush SelectionBrush
  184. {
  185. get => (Brush) GetValue(SelectionBrushProperty);
  186. set => SetValue(SelectionBrushProperty, value);
  187. }
  188. #if !(NET40 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472)
  189. public static readonly DependencyProperty SelectionTextBrushProperty =
  190. TextBoxBase.SelectionTextBrushProperty.AddOwner(typeof(ComboBox));
  191. public Brush SelectionTextBrush
  192. {
  193. get => (Brush) GetValue(SelectionTextBrushProperty);
  194. set => SetValue(SelectionTextBrushProperty, value);
  195. }
  196. #endif
  197. public static readonly DependencyProperty SelectionOpacityProperty =
  198. TextBoxBase.SelectionOpacityProperty.AddOwner(typeof(ComboBox));
  199. public double SelectionOpacity
  200. {
  201. get => (double) GetValue(SelectionOpacityProperty);
  202. set => SetValue(SelectionOpacityProperty, value);
  203. }
  204. public static readonly DependencyProperty CaretBrushProperty =
  205. TextBoxBase.CaretBrushProperty.AddOwner(typeof(ComboBox));
  206. public Brush CaretBrush
  207. {
  208. get => (Brush) GetValue(CaretBrushProperty);
  209. set => SetValue(CaretBrushProperty, value);
  210. }
  211. /// <summary>
  212. /// 更新搜索的项目
  213. /// </summary>
  214. /// <param name="key"></param>
  215. private void UpdateSearchItems(string key)
  216. {
  217. if (_editableTextBox != null && _autoPopupAutoComplete != null)
  218. {
  219. _autoPopupAutoComplete.IsOpen = !string.IsNullOrEmpty(key);
  220. _autoCompletePanel.Children.Clear();
  221. if (SearchFunc == null)
  222. {
  223. if (!string.IsNullOrEmpty(key))
  224. {
  225. foreach (var item in Items)
  226. {
  227. var content = item?.ToString();
  228. if (content == null) continue;
  229. if (!content.Contains(key)) continue;
  230. _autoCompletePanel.Children.Add(CreateSearchItem(item));
  231. }
  232. }
  233. }
  234. else
  235. {
  236. foreach (var item in SearchFunc.Invoke(Items, key))
  237. {
  238. _autoCompletePanel.Children.Add(CreateSearchItem(item));
  239. }
  240. }
  241. }
  242. }
  243. private ComboBoxItem CreateSearchItem(object content)
  244. {
  245. var item = new ComboBoxItem
  246. {
  247. Content = content,
  248. Style = ItemContainerStyle,
  249. ContentTemplate = ItemTemplate
  250. };
  251. item.PreviewMouseLeftButtonDown += AutoCompleteItem_PreviewMouseLeftButtonDown;
  252. return item;
  253. }
  254. private void AutoCompleteItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  255. {
  256. if (sender is ComboBoxItem comboBoxItem)
  257. {
  258. if (_autoPopupAutoComplete != null)
  259. {
  260. _autoPopupAutoComplete.IsOpen = false;
  261. }
  262. _isAutoCompleteAction = false;
  263. SelectedValue = comboBoxItem.Content;
  264. }
  265. }
  266. }