下拉列表

时间:2017-03-30 13:14:39

标签: wpf combobox

使用向上和向下键导航到下拉列表时,不可编辑的Combobox的默认行为是,当前项目突出显示但未选中。仅在输入键上,项目才会被选中。

如果设置IsEditable="True",则行为会有所不同。当前所选项目(和/或文本输入)在下拉列表中通过键盘导航更改。

我的问题是,我根据文本输入过滤项目。当您选择时,您有一个完全匹配,项目计数变为一个。

因此无法用键盘选择正确的项目。

1 个答案:

答案 0 :(得分:1)

受到以下博客文章的启发(谢谢Diederik Krols)我最终找到了解决问题的方法。

http://dotbay.blogspot.de/2009/04/building-filtered-combobox-for-wpf.html

它需要一些额外的工作,但有点反射和绑定暂停,我现在有像预期的组合框行为。

这是我的代码

public enum FilterMode
{
    Contains,
    StartsWith
}

public class FilteredComboBoxBehavior : ManagedBehaviorBase<ComboBox>
{
    private ICollectionView currentView;
    private string currentFilter;
    private Binding textBinding;
    private TextBox textBox;

    private PropertyInfo HighlightedInfoPropetyInfo { get; set; }

    public static readonly DependencyProperty FilterModeProperty = DependencyProperty.Register("FilterMode", typeof(FilterMode), typeof(FilteredComboBoxBehavior), new PropertyMetadata(default(FilterMode)));

    public FilterMode FilterMode
    {
        get
        {
            return (FilterMode)this.GetValue(FilterModeProperty);
        }
        set
        {
            this.SetValue(FilterModeProperty, value);
        }
    }


    public static readonly DependencyProperty OpenDropDownOnFocusProperty = DependencyProperty.Register("OpenDropDownOnFocus", typeof(bool), typeof(FilteredComboBoxBehavior), new PropertyMetadata(true));

    public bool OpenDropDownOnFocus
    {
        get
        {
            return (bool)this.GetValue(OpenDropDownOnFocusProperty);
        }
        set
        {
            this.SetValue(OpenDropDownOnFocusProperty, value);
        }
    }

    protected override void OnSetup()
    {
        base.OnSetup();
        this.AssociatedObject.KeyUp += this.AssociatedObjectOnKeyUp;
        this.AssociatedObject.IsKeyboardFocusWithinChanged += this.OnIsKeyboardFocusWithinChanged;
        this.textBox = this.AssociatedObject.FindChild<TextBox>();

        this.textBinding = BindingOperations.GetBinding(this.AssociatedObject, ComboBox.TextProperty);
        this.HighlightedInfoPropetyInfo = typeof(ComboBox).GetProperty(
            "HighlightedInfo",
            BindingFlags.Instance | BindingFlags.NonPublic);

        var pd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ComboBox));
        pd.AddValueChanged(this.AssociatedObject, this.OnItemsSourceChanged);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        this.AssociatedObject.KeyUp -= this.AssociatedObjectOnKeyUp;
        if (this.currentView != null)
        {
            // ReSharper disable once DelegateSubtraction
            this.currentView.Filter -= this.TextInputFilter;
        }

        BindingOperations.ClearAllBindings(this);
    }


    private void OnItemsSourceChanged(object sender, EventArgs eventArgs)
    {
        this.currentFilter = this.AssociatedObject.Text;
        if (this.currentView != null)
        {
            // ReSharper disable once DelegateSubtraction
            this.currentView.Filter -= this.TextInputFilter;
        }

        if (this.AssociatedObject.ItemsSource != null)
        {
            this.currentView = CollectionViewSource.GetDefaultView(this.AssociatedObject.ItemsSource);
            this.currentView.Filter += this.TextInputFilter;
        }

        this.Refresh();
    }

    private void OnIsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        if (this.AssociatedObject.IsKeyboardFocusWithin)
        {
            this.AssociatedObject.IsDropDownOpen = this.AssociatedObject.IsDropDownOpen || this.OpenDropDownOnFocus;
        }
        else
        {
            this.AssociatedObject.IsDropDownOpen = false;
            this.currentFilter = this.AssociatedObject.Text;
            this.Refresh();
        }
    }

    private void AssociatedObjectOnKeyUp(object sender, KeyEventArgs keyEventArgs)
    {
        if (!this.IsTextManipulationKey(keyEventArgs)
            || (Keyboard.Modifiers.HasAnyFlag() && Keyboard.Modifiers != ModifierKeys.Shift)
            )
        {
            return;
        }

        if (this.currentFilter != this.AssociatedObject.Text)
        {
            this.currentFilter = this.AssociatedObject.Text;
            this.Refresh();
        }
    }

    private bool TextInputFilter(object obj)
    {
        var stringValue = obj as string;
        if (obj != null && !(obj is string))
        {
            var path = (string)this.GetValue(TextSearch.TextPathProperty);
            if (path != null)
            {
                stringValue = obj.GetType().GetProperty(path).GetValue(obj) as string;
            }
        }

        if (stringValue == null)
            return false;


        switch (this.FilterMode)
        {
            case FilterMode.Contains:
                return stringValue.IndexOf(this.currentFilter, StringComparison.OrdinalIgnoreCase) >= 0;
            case FilterMode.StartsWith:
                return stringValue.StartsWith(this.currentFilter, StringComparison.OrdinalIgnoreCase);
            default:
                throw new ArgumentOutOfRangeException();
        }
    }

    private bool IsTextManipulationKey(KeyEventArgs keyEventArgs)
    {
        return keyEventArgs.Key == Key.Back 
            || keyEventArgs.Key == Key.Space 
            || (keyEventArgs.Key >= Key.D0 && keyEventArgs.Key <= Key.Z)
            || (Keyboard.IsKeyToggled(Key.NumLock) && keyEventArgs.Key >= Key.NumPad0 && keyEventArgs.Key <= Key.NumPad9)
            || (keyEventArgs.Key >= Key.Multiply && keyEventArgs.Key <= Key.Divide)
            || (keyEventArgs.Key >= Key.Oem1 && keyEventArgs.Key <= Key.OemBackslash);

    }

    private void Refresh()
    {
        if (this.currentView != null)
        {
            var tempCurrentFilter = this.AssociatedObject.Text;
            using (new SuspendBinding(this.textBinding, this.AssociatedObject, ComboBox.TextProperty))
            {
                this.currentView.Refresh();
                //reset internal highlighted info
                this.HighlightedInfoPropetyInfo.SetValue(this.AssociatedObject, null);
                this.AssociatedObject.SelectedIndex = -1;
                this.AssociatedObject.Text = tempCurrentFilter;

            }

            if (this.textBox != null && tempCurrentFilter != null)
            {
                this.textBox.SelectionStart = tempCurrentFilter.Length;
                this.textBox.SelectionLength = 0;
            }
        }
    }
}

/// <summary>
/// Temporarely suspend binding on dependency property
/// </summary>
public class SuspendBinding : IDisposable
{
    private readonly Binding bindingToSuspend;

    private readonly DependencyObject target;

    private readonly DependencyProperty property;

    public SuspendBinding(Binding bindingToSuspend, DependencyObject target, DependencyProperty property)
    {
        this.bindingToSuspend = bindingToSuspend;
        this.target = target;
        this.property = property;
        BindingOperations.ClearBinding(target, property);
    }

    public void Dispose()
    {
        BindingOperations.SetBinding(this.target, this.property, this.bindingToSuspend);
    }
}


public abstract class ManagedBehaviorBase<T> : Behavior<T> where T : FrameworkElement
{
    private bool isSetup;
    private bool isHookedUp;
    private WeakReference weakTarget;

    protected virtual void OnSetup() { }
    protected virtual void OnCleanup() { }
    protected override void OnChanged()
    {
        var target = this.AssociatedObject;
        if (target != null)
        {
            this.HookupBehavior(target);
        }
        else
        {
            this.UnHookupBehavior();
        }
    }

    private void OnTargetLoaded(object sender, RoutedEventArgs e) { this.SetupBehavior(); }

    private void OnTargetUnloaded(object sender, RoutedEventArgs e) { this.CleanupBehavior(); }

    private void HookupBehavior(T target)
    {
        if (this.isHookedUp) return;
        this.weakTarget = new WeakReference(target);
        this.isHookedUp = true;
        target.Unloaded += this.OnTargetUnloaded;
        target.Loaded += this.OnTargetLoaded;
        if (target.IsLoaded)
        {
            this.SetupBehavior();
        }
    }

    private void UnHookupBehavior()
    {
        if (!this.isHookedUp) return;
        this.isHookedUp = false;
        var target = this.AssociatedObject ?? (T)this.weakTarget.Target;
        if (target != null)
        {
            target.Unloaded -= this.OnTargetUnloaded;
            target.Loaded -= this.OnTargetLoaded;
        }
        this.CleanupBehavior();
    }

    private void SetupBehavior()
    {
        if (this.isSetup) return;
        this.isSetup = true;
        this.OnSetup();
    }

    private void CleanupBehavior()
    {
        if (!this.isSetup) return;
        this.isSetup = false;
        this.OnCleanup();
    }
}

XAML

<ComboBox IsEditable="True"
      Text="{Binding Path=ZipCode, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
      ItemsSource="{Binding Path=PostalCodes}"
      IsTextSearchEnabled="False" 
      behaviors:AttachedMaxLength.ChildTextBoxMaxLength="{Binding Path=ZipCodeMaxLength}">
<i:Interaction.Behaviors>
    <behaviors:FilteredComboBoxBehavior FilterMode="StartsWith"/>
</i:Interaction.Behaviors>