У нас есть ComboBox с DataGrid, основанный на этой статье .
Теперь у нас нет возможности фильтровать значения. Поэтому я реализовал этот .
Фильтрация работает нормально, и на клавиатуре можно выбирать подсказки вверх и вниз. Но невозможно выбрать один с помощью мыши.
Почему это невозможно? Как я могу это исправить?
Вот код нашего комбинированного списка:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
namespace CustomControls {
[DefaultProperty("Columns")]
[ContentProperty("Columns")]
[TemplatePart(Name = s_partPopupDataGrid, Type = typeof(DataGrid))]
public class GridCombo : ComboBox {
#region Static
internal static readonly DependencyProperty ReplaceColumnsProperty =
DependencyProperty.Register(
"ReplaceColumns",
typeof(IEnumerable<DataGridBoundColumn>),
typeof(GridCombo),
new FrameworkPropertyMetadata());
public static readonly DependencyProperty CellStyleProperty =
DependencyProperty.Register(
"CellStyle",
typeof(Style),
typeof(GridCombo),
new FrameworkPropertyMetadata());
public static readonly DependencyProperty MinimumSearchLengthProperty =
DependencyProperty.Register(
"MinimumSearchLength",
typeof(int),
typeof(GridCombo),
new UIPropertyMetadata(1));
static GridCombo() {
DefaultStyleKeyProperty.OverrideMetadata(
typeof(GridCombo), new FrameworkPropertyMetadata(typeof(GridCombo)));
}
#endregion
// ======================================================================
#region Fields & Constructors
private const string s_partPopupDataGrid = "PART_PopupDataGrid";
// Columns of DataGrid
private ObservableCollection<DataGridBoundColumn> _columns;
private readonly Dictionary<Type, List<PropertyInfo>> _properties = new Dictionary<Type, List<PropertyInfo>>();
// Attached DataGrid control
private DataGrid _popupDataGrid;
private Popup _popup;
private string _oldFilter = string.Empty;
private string _currentFilter = string.Empty;
#endregion
// ======================================================================
#region Public
public Style CellStyle {
get { return (Style)GetValue(CellStyleProperty); }
set { SetValue(CellStyleProperty, value); }
}
/// <summary>
/// If set, the "Columns" property is ignored. Useful if you need
/// a dependency property.
/// </summary>
internal IEnumerable<DataGridBoundColumn> ReplaceColumns {
get { return (ObservableCollection<DataGridBoundColumn>)GetValue(ReplaceColumnsProperty); }
set { SetValue(ReplaceColumnsProperty, value); }
}
// The property is default and Content property for CustComboBox
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObservableCollection<DataGridBoundColumn> Columns {
get {
if (_columns == null) {
_columns = new ObservableCollection<DataGridBoundColumn>();
}
return _columns;
}
}
// Apply theme and attach columns to DataGrid popup control
public override void OnApplyTemplate() {
if (_popupDataGrid == null) {
_popupDataGrid = Template.FindName(s_partPopupDataGrid, this) as DataGrid;
if (_popupDataGrid != null && (_columns != null || ReplaceColumns != null)) {
if (ReplaceColumns != null) {
foreach (var column in ReplaceColumns) {
var copy = DataGridFix.CopyDataGridColumn(column);
_popupDataGrid.Columns.Add(copy);
}
} else {
// Add columns to DataGrid columns
for (int i = 0; i < _columns.Count; i++)
_popupDataGrid.Columns.Add(_columns[i]);
}
// Add event handler for DataGrid popup
_popupDataGrid.MouseDown += PopupDataGridMouseDown;
_popupDataGrid.SelectionChanged += PopupDataGridSelectionChanged;
}
}
if (_popup == null) {
_popup = Template.FindName("PART_Popup", this) as Popup;
if (_popup != null && _popupDataGrid != null) {
_popup.Opened += PopupOpened;
_popup.Focusable = true;
}
}
// Call base class method
base.OnApplyTemplate();
}
[Description("Length of the search string that triggers filtering.")]
[Category("Filtered ComboBox")]
[DefaultValue(1)]
public int MinimumSearchLength {
[DebuggerStepThrough]
get { return (int)GetValue(MinimumSearchLengthProperty); }
[DebuggerStepThrough]
set { SetValue(MinimumSearchLengthProperty, value); }
}
#endregion
// ======================================================================
#region Protected
// When selection changed in combobox (pressing arrow key down or up) must be synchronized with opened DataGrid popup
protected override void OnSelectionChanged(SelectionChangedEventArgs e) {
base.OnSelectionChanged(e);
if (_popupDataGrid == null)
return;
if (!DesignerProperties.GetIsInDesignMode(this)) {
if (IsDropDownOpen) {
_popupDataGrid.SelectedItem = SelectedItem;
ScrollIntoView(SelectedItem);
}
}
}
protected override void OnDropDownOpened(EventArgs e) {
if (_popupDataGrid == null)
return;
_popupDataGrid.SelectedItem = SelectedItem;
base.OnDropDownOpened(e);
}
protected TextBox EditableTextBox {
get { return Template.FindName("PART_EditableTextBox", this) as TextBox; }
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e) {
if (!IsEditable) {
base.OnPreviewLostKeyboardFocus(e);
return;
}
ClearFilter();
int temp = SelectedIndex;
SelectedIndex = -1;
Text = string.Empty;
SelectedIndex = temp;
base.OnPreviewLostKeyboardFocus(e);
}
protected override void OnKeyUp(KeyEventArgs e) {
if (!IsEditable) {
base.OnKeyUp(e);
return;
}
if (e.Key == Key.Up || e.Key == Key.Down) {
// Navigation keys are ignored
} else if (e.Key == Key.Tab || e.Key == Key.Enter) {
// Explicit Select -> Clear Filter
ClearFilter();
} else {
// The text was changed
if (Text != _oldFilter) {
// Clear the filter if the text is empty,
// apply the filter if the text is long enough
if (Text.Length == 0 || Text.Length >= MinimumSearchLength) {
RefreshFilter();
IsDropDownOpen = true;
// Unselect
EditableTextBox.SelectionStart = int.MaxValue;
}
}
base.OnKeyUp(e);
_currentFilter = Text;
}
}
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) {
if (!IsEditable) {
base.OnItemsSourceChanged(oldValue, newValue);
return;
}
if (newValue != null) {
var view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += FilterPredicate;
}
if (oldValue != null) {
var view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
protected override void OnPreviewKeyDown(KeyEventArgs e) {
if (!IsEditable) {
base.OnPreviewKeyDown(e);
return;
}
if (e.Key == Key.Tab || e.Key == Key.Enter) {
// Explicit Selection -> Close ItemsPanel
IsDropDownOpen = false;
} else if (e.Key == Key.Escape) {
// Escape -> Close DropDown and redisplay Filter
IsDropDownOpen = false;
SelectedIndex = -1;
Text = _currentFilter;
} else {
if (e.Key == Key.Down) {
// Arrow Down -> Open DropDown
IsDropDownOpen = true;
}
base.OnPreviewKeyDown(e);
}
_oldFilter = Text;
}
#endregion
// ======================================================================
#region Private
private void RefreshFilter() {
if (ItemsSource != null) {
var view = CollectionViewSource.GetDefaultView(ItemsSource);
view.Refresh();
}
}
private void ClearFilter() {
_currentFilter = string.Empty;
RefreshFilter();
}
private bool FilterPredicate(object value) {
if (value == null) {
return false;
}
if (Text.Length == 0) {
return true;
}
var properties = GetProperties(value.GetType());
foreach (var property in properties) {
var propertyValue = (property.GetValue(value, null) ?? string.Empty).ToString();
if (propertyValue.ToLowerInvariant().Contains(Text.ToLowerInvariant())) {
return true;
}
}
return false;
}
private IEnumerable<PropertyInfo> GetProperties(Type type) {
if (!_properties.ContainsKey(type)) {
_properties.Add(type, new List<PropertyInfo>());
foreach (var column in _columns) {
if (column.Binding != null && column.Binding is Binding) {
var path = ((Binding)column.Binding).Path.Path;
var property = type.GetProperty(path);
if (property != null) {
_properties[type].Add(property);
}
}
}
}
return _properties[type];
}
private void PopupOpened(object sender, EventArgs e) {
ScrollIntoView(SelectedItem);
}
private void ScrollIntoView(object item) {
if (item != null && _popupDataGrid.Items.Contains(item))
_popupDataGrid.ScrollIntoView(item);
}
// Synchronize selection between Combo and DataGrid popup
private void PopupDataGridSelectionChanged(object sender, SelectionChangedEventArgs e) {
// When open in Blend prevent raising exception
if (!DesignerProperties.GetIsInDesignMode(this)) {
var grid = sender as DataGrid;
if (grid != null && grid.IsVisible) {
SelectedItem = grid.SelectedItem;
}
}
}
// Event for DataGrid popup MouseDown
private void PopupDataGridMouseDown(object sender, MouseButtonEventArgs e) {
DataGrid dg = sender as DataGrid;
if (dg != null) {
var dep = (DependencyObject)e.OriginalSource;
// iteratively traverse the visual tree and stop when dep is one of ..
while ((dep != null) &&
!(dep is DataGridCell) &&
!(dep is DataGridColumnHeader)) {
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
if (dep is DataGridColumnHeader) {
// do something
}
// When user clicks to DataGrid cell, popup have to be closed
if (dep is DataGridCell) {
IsDropDownOpen = false;
}
}
}
#endregion
}
}
Для проверки можно использовать следующий Xaml:
<Window x:Class="GridComboTestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:combo="clr-namespace:CustomControls;assembly=CustomControls"
Height="300" Width="300">
<StackPanel>
<Button Content="ChangeElements" Command="{Binding ChangeElements}" />
<combo:GridCombo
x:Name="GridCombo"
ItemsSource="{Binding Elements}"
DisplayMemberPath="Number"
IsEditable="True"
SelectedItem="{Binding SelectedElement}"
MaxDropDownHeight="100">
<DataGridTextColumn Binding="{Binding Number, Mode=OneWay}" />
<DataGridTextColumn Binding="{Binding Name, Mode=OneWay}" />
</combo:GridCombo>
</StackPanel>
</Window>