<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CDS3.ControlsWPF"> <Style TargetType="{x:Type local:cdsPopUpListBox}"> <Setter Property="Margin" Value="1"/> <Setter Property="Template"> <!-- ScrollViewer must be set --> <Setter.Value> <ControlTemplate TargetType="{x:Type local:cdsPopUpListBox}"> <Border Name="Border" Background="WhiteSmoke" BorderBrush="Black" BorderThickness="1"> <ScrollViewer Margin="0" Focusable="false" VerticalScrollBarVisibility="Auto"> <StackPanel Margin="2" IsItemsHost="True" /> </ScrollViewer> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type local:cdsAutoComplete}"> <Setter Property="Template"> <!--Create a TextBox that looks "flat". The control template for a TextBox or RichTextBox must include an element tagged as the content host. An element is tagged as the content host element when it has the special name PART_ContentHost. The content host element must be a ScrollViewer, or an element that derives from Decorator. --> <Setter.Value> <ControlTemplate TargetType="{x:Type local:cdsAutoComplete}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="2,4,2,2"> <Decorator x:Name="PART_ContentHost"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>It took me forever to figure out that I had to have ScrollViewer in there I only managed to figure it out by trial and error. A number of the attributes in there should probably be set back to TemplateBinding.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace CDS3.ControlsWPF { /// <summary> /// Popup List box. /// </summary> public partial class cdsPopUpListBox : System.Windows.Controls.ListBox { //http://blogs.msdn.com/wpfsdk/archive/2007/04/27/popup-your-control.aspx Popup _parentPopup; public cdsPopUpListBox() : base() { LostKeyboardFocus += new KeyboardFocusChangedEventHandler(cdsPopUpListBox_LostKeyboardFocus); } static cdsPopUpListBox() { //This OverrideMetadata call tells the system that this element //wants to provide a style that is different than its base class. //This style is defined in themes\generic.xaml DefaultStyleKeyProperty.OverrideMetadata(typeof(cdsPopUpListBox), new FrameworkPropertyMetadata(typeof(cdsPopUpListBox))); } void cdsPopUpListBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { if (e.NewFocus == null) IsOpen = false; else if (!e.NewFocus.ToString().StartsWith("System.Windows.Controls.ListBoxItem:")) IsOpen = false; } //Placement public static readonly DependencyProperty PlacementProperty = Popup.PlacementProperty.AddOwner(typeof(cdsPopUpListBox)); public PlacementMode Placement { get { return (PlacementMode)GetValue(PlacementProperty); } set { SetValue(PlacementProperty, value); } } //PlacementTarget public static readonly DependencyProperty PlacementTargetProperty = Popup.PlacementTargetProperty.AddOwner(typeof(cdsPopUpListBox)); public UIElement PlacementTarget { get { return (UIElement)GetValue(PlacementTargetProperty); } set { SetValue(PlacementTargetProperty, value); } } //PlacementRectangle public static readonly DependencyProperty PlacementRectangleProperty = Popup.PlacementRectangleProperty.AddOwner(typeof(cdsPopUpListBox)); public Rect PlacementRectangle { get { return (Rect)GetValue(PlacementRectangleProperty); } set { SetValue(PlacementRectangleProperty, value); } } //HorizontalOffset public static readonly DependencyProperty HorizontalOffsetProperty = Popup.HorizontalOffsetProperty.AddOwner(typeof(cdsPopUpListBox)); public double HorizontalOffset { get { return (double)GetValue(HorizontalOffsetProperty); } set { SetValue(HorizontalOffsetProperty, value); } } //VerticalOffset public static readonly DependencyProperty VerticalOffsetProperty = Popup.VerticalOffsetProperty.AddOwner(typeof(cdsPopUpListBox)); public double VerticalOffset { get { return (double)GetValue(VerticalOffsetProperty); } set { SetValue(VerticalOffsetProperty, value); } } public static readonly DependencyProperty IsOpenProperty = Popup.IsOpenProperty.AddOwner( typeof(cdsPopUpListBox), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnIsOpenChanged))); public bool IsOpen { get { return (bool)GetValue(IsOpenProperty); } set { SetValue(IsOpenProperty, value); } } private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { cdsPopUpListBox ctrl = (cdsPopUpListBox)d; if ((bool)e.NewValue) { if (ctrl._parentPopup == null) { ctrl.HookupParentPopup(); } } } private void HookupParentPopup() { _parentPopup = new Popup(); _parentPopup.AllowsTransparency = true; Popup.CreateRootPopup(_parentPopup, this); } } }The source for most of that code came from the link next to the class definition. I just changed it from a textbox to a listbox.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Markup; namespace CDS3.ControlsWPF { /// <summary> /// Auto complete TextBox /// /// current project: xmlns:MyNamespace="clr-namespace:CDS3.ControlsWPF" /// different project: xmlns:MyNamespace="clr-namespace:CDS3.ControlsWPF;assembly=CDS3.ControlsWPF" /// project reference : /// /// Right click on the target project in the Solution Explorer and /// "Add Reference"->"Projects"->[Browse to and select this project] /// /// use your control in the XAML file: <MyNamespace:cdsAutoComplete/> /// </summary> public class cdsAutoComplete : TextBox { public IEnumerable<cdsAutoCompleteItems> AutoCompleteList; public bool Constrain2List { get; set; } private int _ID; public int ID { get { return _ID; } set { _ID = value; string nm = (from items in AutoCompleteList where items.ID == value select items.Name).First(); this.Text = nm; } } private cdsPopUpListBox popup; #region Constructors public cdsAutoComplete() : base() { popup = new cdsPopUpListBox(); popup.PlacementTarget = this; LostKeyboardFocus += new KeyboardFocusChangedEventHandler(cdsAutoComplete_LostKeyboardFocus); KeyUp += new KeyEventHandler(cdsAutoComplete_KeyUp); popup.KeyUp += new KeyEventHandler(popup_KeyUp); Constrain2List = false; } static cdsAutoComplete() { DefaultStyleKeyProperty.OverrideMetadata(typeof(cdsAutoComplete), new FrameworkPropertyMetadata(typeof(cdsAutoComplete))); } #endregion #region Events void cdsAutoComplete_KeyUp(object sender, KeyEventArgs e) { //var xConverter = new KeyConverter(); //char xChar = xConverter.ConvertToString(e.Key)[0]; if (e.Key == System.Windows.Input.Key.Enter) TogglePopup(false); else if (e.Key == System.Windows.Input.Key.Down) { object item = popup.Items[0]; popup.SelectedItem = item; ListBoxItem lbi = (ListBoxItem)popup.ItemContainerGenerator.ContainerFromItem(item); lbi.Focus(); } else { string entered = ((TextBox)sender).Text; popup.Items.Clear(); var selected = (from items in AutoCompleteList where items.Name.ToUpper().StartsWith(entered.ToUpper()) select items).Take(5); if (selected.Count()>0) { int selectstart = this.SelectionStart; this.Text = selected.First().Name; this.SelectionStart = selectstart; this.SelectionLength = this.Text.Length - selectstart; //System.Windows.Forms.MessageBox.Show(this.SelectionStart.ToString()); this.Text = selected.First().Name; foreach (var sel in selected) { popup.Items.Add(sel.Name); } TogglePopup(true); } else TogglePopup(false); } } void cdsAutoComplete_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { if (e.NewFocus == null) popup.IsOpen = false; else if (!e.NewFocus.ToString().StartsWith("System.Windows.Controls.ListBoxItem:")) popup.IsOpen = false; } void popup_KeyUp(object sender, KeyEventArgs e) { if (e.Key == System.Windows.Input.Key.Enter) { this.Text = popup.SelectedItem.ToString(); TogglePopup(false); } } #endregion void TogglePopup(bool showPopup) { popup.MinWidth = this.ActualWidth; popup.IsOpen = showPopup; } } public class cdsAutoCompleteItems { public int ID { get; set; } public string Name { get; set; } public cdsAutoCompleteItems(int id,string name) { ID = id; Name = name; } } }Now that code is still unfinished, and is real rough. I have more events to handle and more keyboard handling still to write, features to add, etc etc. Parts of it have not even been tested yet.
cdsPayablesDCDataContext dc = new cdsPayablesDCDataContext(); AutoCompBox.AutoCompleteList = from acts in dc.cdsEntities where acts.RecordType == 'A' select new cdsAutoCompleteItems(acts.ID, acts.Name.Trim());And to use it:
<!-- in the host controls header xmlns:local="clr-namespace:CDS3.ControlsWPF" --> <Grid.Resources> <Style TargetType="local:cdsAutoComplete"> <Setter Property="BorderThickness" Value="1" /> <Setter Property="Background" Value="White"/> <Setter Property="Width" Value="250"/> </Style> </Grid.Resources> <local:cdsAutoComplete Height="23" HorizontalAlignment="Left" Margin="43,15,0,0" x:Name="AutoComp" VerticalAlignment="Top" Constrain2List ="True" Width="140" BorderBrush="#FFA5ACB2" />As I mentioned above, it's still pretty rough, and missing a number of event handlers.