Search-As-You-Type für Combo-Box

Trotz aller Raffinessen der WPF-Komponenten von DevExpress bin ich auf einen störenden Mangel bei der ComboBox (DevExpress.Xpf.Editors.ComboBoxEdit) gestoßen. Denn es fehlt dort eine Keyboard-unterstützende Funktion, die ich eigentlich nahezu bei allen langlistigen Combo-Boxen nutze, die Funktion Search-As-You-Type. Dabei besteht die Möglichkeit, bei fokussierter Combo-Box einen Buchstaben auf der Tastatur einzugeben, worauf das erste Element mit dem entsprechenden Anfangsbuchstaben ausgewählt wird. Bei mehrmaligem Drücken des gleichen Buchstabens kann man nacheinander alle weiteren Listenelemente mit diesem Anfangsbuchstaben selektieren. Damit entfällt das mühsame Klicken und Scrollen mit der Maus.

Ich muss präziser werden: diese Funktion wird unterstützt, jedoch nur bei Combo-Boxen, die editierbar sind, in denen also eigene Einträge hinzugefügt werden können. Solche Steuerelemente sollten meiner Meinung nach jedoch in einer ausgewogenen und intuitiven Benutzeroberfläche die Ausnahme sein.

Meine entsprechende Supportanfrage ist hier zu finden (und wurde auch – großes Lob – sehr schnell bearbeitet!):
http://www.devexpress.com/Support/Center/p/Q401598.aspx

Ich habe also selbst ein Behavior geschrieben, das ich hier zur allgemeinen Verfügung stellen will.

Anwendung:

<dxe:ComboBoxEdit ItemsSource="{Binding Path=Items}"
                  DisplayMember="DisplayText"
                  SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}">
  <i:Interaction.Behaviors>
    <behaviors:ComboBoxKeySelectBehavior />
  </i:Interaction.Behaviors>
</dxe:ComboBoxEdit>

Hinweis: für die Behaviors brauchen wir eine Referenz zu System.Windows.Interactivity

Im ComboBoxKeySelectBehavior wird auf das KeyDown-Event des Steuerelements gelauscht. Wird dieses Event ausgelöst, wird die gedrückte Taste ausgewertet und dann entsprechend die Eigenschaft ComboBoxEdit.SelectedItem gesetzt.

Unsere Behaviors benutzen übrigens alle eine eigene Basisklasse, in der sichergestellt ist, dass es auch wieder richtig abgehängt wird. Denn leider scheint es eine bekannte Schwachstelle zu geben, dass das Behavior.OnDetaching() nicht verlässlich aufgerufen wird, wenn das Element aus dem Visual-Tree entfernt wird. Um das sicherzustellen, benutzen wir in der Basisklasse das Event FrameworkElement.Unloaded().

using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using DevExpress.Xpf.Editors;
using comWORK.UI.WPF.Contracts.Behaviors;

namespace comWORK.UI.WPF.Controls.LabeledControls.ComboBox
{
    public class ComboBoxKeySelectBehavior : BehaviorBase<ComboBoxEdit>
    {
        private string _lastKeyChar;
        private int _repeatedCharCounter;

        protected override void Initialize()
        {
            AssociatedObject.KeyDown += ComboBoxKeyDown;
        }

        protected override void Release()
        {
            AssociatedObject.KeyDown -= ComboBoxKeyDown;
        }

        private void ComboBoxKeyDown(object sender, KeyEventArgs args)
        {
            var items = AssociatedObject.ItemsSource as IEnumerable<ComboBoxItemVM>;
            if (items == null)
                return;

            string key = args.Key.ToString();
            var itemToSelect = FindItemToSelect(items, key);
            if (itemToSelect != null)
                AssociatedObject.SelectedItem = itemToSelect;

            _lastKeyChar = key;
        }

        private ComboBoxItemVM FindItemToSelect(IEnumerable<ComboBoxItemVM> items, string key)
        {
            ComboBoxItemVM result;
            if (_lastKeyChar != key)
            {
                _repeatedCharCounter = 0;
                result = items.FirstOrDefault(x => x.DisplayText.StartsWith(key));
            }
            else
            {
                _repeatedCharCounter++;
                result = items.Where(x => x.DisplayText.StartsWith(key)).Take(_repeatedCharCounter + 1).LastOrDefault();
            }
            return result;
        }
    }
}

Und die Behavior-Basisklasse:

using System;
using System.Linq;
using System.Windows;
using System.Windows.Interactivity;

namespace comWORK.UI.WPF.Contracts.Behaviors
{
    /// <summary>
    /// Ensures that OnDetach is called at least when FrameworkElement is being unloaded.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class BehaviorBase<T> : Behavior<T> where T : FrameworkElement
    {
        private bool _isClean = true;

        protected override sealed void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.Unloaded += OnAssociatedObjectUnloaded;
            _isClean = false;

            Initialize();
        }

        protected override sealed void OnDetaching()
        {
            CleanUp();
            base.OnDetaching();
        }

        /// <summary>
        ///   Initializes the behavior.
        /// </summary>
        protected abstract void Initialize();

        /// <summary>
        ///   Is called at least on Unload of AssociatedObject.
        /// </summary>
        protected abstract void Release();

        private void OnAssociatedObjectUnloaded(object sender, EventArgs e)
        {
            CleanUp();
        }

        private void CleanUp()
        {
            if (_isClean)
                return;

            _isClean = true;

            if (AssociatedObject != null)
                AssociatedObject.Unloaded -= OnAssociatedObjectUnloaded;

            Release();
        }
    }
}

– Verfasser: Alexander Ziegler

Advertisements

Über hecogmbh

Die heco gmbh ist ein Handelshaus sowie ein Produzent für Fittings und Armaturen aus rostfreiem Edelstahl. Unsere Produkte werden im Maschinen-, Anlagen- und Fahrzeugbau sowohl in der chemischen Industrie als auch in der Nahrungsmittelindustrie eingesetzt. Mit mehreren Standorten europaweit sind wir immer in Ihrer Nähe.
Dieser Beitrag wurde unter Alexander Ziegler abgelegt und mit verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s