Выделение мышью в DataGrid невозможно с отфильтрованной коллекцией - PullRequest
0 голосов
/ 10 августа 2011

У нас есть 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>

1 Ответ

0 голосов
/ 10 августа 2011

Вы не вызываете base () для PopupDataGridMouseDown. Не уверен, что это все исправит, но на что-то посмотреть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...