WPF Datagrid as ComboBox Dropdown Part 2
By Michael Detras
This presents a WPF custom control derived from ComboBox that shows a DataGrid to display the ComboBox items.
Introduction
Some time ago, I have written an article that discusses how to show ComboBox items using a DataGrid. The article shows how
to change the control template of the ComboBox and replace the default control
in the Popup with a DataGrid. It gets the job done but is not elegant, I must
admit. If you want to do the same thing on other ComboBox controls, then you
have to replicate the style and control template and change the columns of the
DataGrid (since you probably don’t want to show the same columns over and over).
This may result in long lines of XAML codes if there are a lot of ComboBox controls.
It is pretty messy in my opinion.
I think that this issue can be resolved in different ways. It might be possible to
create a style and use data triggers to select the columns that you want to display.
However, I do not prefer this solution since it is not very intuitive. We have
to look at the style definition that could be located at another file and check
a long list of data triggers just to add or remove some DataGrid columns. We
are left with creating either a user control or custom control. Creating a user
control is easy since doing this is just like dragging controls to a Window.
Yet, I am not really going to drag a lot of controls, only one ComboBox. Besides,
I want the control to still be a ComboBox. If I created a user control, then
it is very likely that I have to expose the properties of the ComboBox. That
is just another level of indirection. Thus, my choice of implementation is to
create a custom control.
Deriving from ComboBox
Before anything else, first create a project of type WPF Custom Control Library,
so that things are automatically created for us. Our first goal is to create
a custom control that derives from ComboBox and create a property where its value
will be used as the content of the dropdown (or Popup control). We can create
a dependency property of type UIElement for this.
public class ExtendedComboBox : ComboBox
{
#region Dependency Properties
public static readonly DependencyProperty PopupContentProperty =
DependencyProperty.Register("PopupContent", typeof(UIElement), typeof(ExtendedComboBox));
public UIElement PopupContent
{
get { return (UIElement)GetValue(PopupContentProperty); }
set { SetValue(PopupContentProperty, value); }
}
#endregion
#region Constructor
static ExtendedComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedComboBox), new FrameworkPropertyMetadata(typeof(ExtendedComboBox)));
}
#endregion
}
Listing 1. ComboBox with PopupContent Property
Now, we can reference the PopupContent dependency property in the default style of
the ExtendedComboBox control found in Generic.xaml. I copied the XAML code in
my previous article and changed some lines. I only copied the part that we are
interested in, which is shown in the following code listing. The highlighted
part shows the changes.
<Popup
AllowsTransparency="true"
IsOpen="{Binding Path=IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"
Placement="Bottom"
PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"
Margin="1"
x:Name="PART_Popup"
Grid.ColumnSpan="2">
<Microsoft_Windows_Themes:SystemDropShadowChrome
MaxHeight="{TemplateBinding MaxDropDownHeight}"
MinWidth="{Binding Path=ActualWidth, ElementName=MainGrid}"
x:Name="Shdw"
Color="Transparent">
<Border
x:Name="DropDownBorder"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"
BorderThickness="1">
<ContentControl
Content="{TemplateBinding PopupContent}"/>
</Border>
</Microsoft_Windows_Themes:SystemDropShadowChrome>
</Popup>
Listing 2. ExtendedComboBox Default Style
As you can see, the PopupContent’s value will be the content of the ContentControl
that we added inside the Popup control. It’s up to you if you want to reference
the PopupContent somewhere else. You could remove the SystemDropShadowChrome
entirely and bind the Popup’s Child property to the PopupContent.
Adding a DataGrid
Let’s use the control in a demo application and check if it already satisfies our
requirement. I have reused most of the codes in my previous article like the
Customer and Customer classes, as well as the code-behind file for the main window,
so I suggest you check it out.
<Window
x:Class="DemoApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CustomControls;assembly=CustomControls"
xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:local="clr-namespace:DemoApp"
Title="MainWindow" Height="300" Width="300">
<Grid>
<Grid.Resources>
<local:Customers x:Key="Customers"/>
</Grid.Resources>
<controls:ExtendedComboBox
Width="250"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ItemsSource="{StaticResource Customers}"
SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type
Window}}, Path=SelectedCustomer}"
IsDropDownOpen="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type
Window}}, Path=IsEditingCustomer}">
<controls:ExtendedComboBox.PopupContent>
<toolkit:DataGrid
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type
controls:ExtendedComboBox}}, Path=ItemsSource}"
SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type
controls:ExtendedComboBox}}, Path=SelectedItem}">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTextColumn
Header="Name"
Binding="{Binding Name}"/>
<toolkit:DataGridTextColumn
Header="Address"
Binding="{Binding Address}"/>
<toolkit:DataGridTextColumn
Header="Telephone No."
Binding="{Binding TelephoneNumber}"/>
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
</controls:ExtendedComboBox.PopupContent>
<controls:ExtendedComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Margin="4,0"/>
<TextBlock Text="{Binding Path=Address}" Margin="4,0"/>
<TextBlock Text="{Binding Path=TelephoneNumber}" Margin="4,0"/>
</StackPanel>
</DataTemplate>
</controls:ExtendedComboBox.ItemTemplate>
</controls:ExtendedComboBox>
</Grid>
</Window>
Listing 3. Using the ExtendedComboBox Control
The ComboBox’s ItemsSource, SelectedItem and IsDropDownOpen properties are bounded
to properties that are found in the MainWindow's code-behind file. The ItemsSource
and SelectedItem properties of the DataGrid are then bounded to the matching
properties of the ComboBox. The IsDropDownOpen property is used so that the Popup
is closed when a different item in the DataGrid is selected. The following figure
shows the demo application.

Figure 1. DataGrid in ComboBox Demo
Further Improvements
With the current control, we can have a DataGrid inside a ComboBox without much replication
of codes. However, there are some things that we can still improve. First, we
may want to remove the dependence on using IsDropDownOpen and IsEditingCustomer
properties just to close the ComboBox’s Popup. I believe the user of the control
does not need to be concerned with closing the Popup. The following shows two
properties in the current code-behind file of the main window. These properties
are used for closing the Popup. When the SelectedCustomer property value changes,
it sets the IsEditingCustomer property to false, thus closing the Popup since
the IsDropDownOpen property is bounded to the IsEditingProperty. This is really
not the best solution.
public Customer SelectedCustomer
{
get
{
return selectedCustomer;
}
set
{
if (object.ReferenceEquals(selectedCustomer, value) != true)
{
selectedCustomer = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedCustomer"));
}
IsEditingCustomer = false;
}
}
}
public bool IsEditingCustomer
{
get
{
return isEditingCustomer;
}
set
{
if (isEditingCustomer.Equals(value) != true)
{
isEditingCustomer = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("IsEditingCustomer"));
}
}
}
}
Listing 4. Current Code for Closing the Popup
Second, we will always write the code to bind the ItemsSource and SelectedItem properties
of the DataGrid to the matching properties of the ComboBox every time we use
the control. It is nice if this could be done only once. To solve these problems,
let’s create another control that will inherit from the ExtendedComboBox class.
public class ExtendedComboBoxDataGrid : ExtendedComboBox
{
#region Private Fields
private DataGrid dataGrid;
#endregion
#region Dependency Properties
public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns",
typeof(ObservableCollection<DataGridColumn>), typeof(ExtendedComboBoxDataGrid));
public ObservableCollection<DataGridColumn> Columns
{
get { return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
#endregion
#region Constructors
public ExtendedComboBoxDataGrid()
{
dataGrid = new DataGrid();
dataGrid.IsReadOnly = true;
dataGrid.AutoGenerateColumns = false;
RelativeSource relativeSource = new RelativeSource(RelativeSourceMode.FindAncestor,
typeof(ExtendedComboBoxDataGrid), 1);
// Bind ItemsSource
Binding itemsSourceBinding = new Binding("ItemsSource");
itemsSourceBinding.RelativeSource = relativeSource;
dataGrid.SetBinding(DataGrid.ItemsSourceProperty, itemsSourceBinding);
// Bind SelectedItem
Binding selectedItemBinding = new Binding("SelectedItem");
selectedItemBinding.RelativeSource = relativeSource;
dataGrid.SetBinding(DataGrid.SelectedItemProperty, selectedItemBinding);
dataGrid.SelectionChanged += DataGrid_SelectionChanged;
PopupContent = dataGrid;
Columns = new ObservableCollection<DataGridColumn>();
Columns.CollectionChanged += Columns_CollectionChanged;
}
#endregion
#region Private Methods
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
IsDropDownOpen = false;
}
private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (DataGridColumn column in e.NewItems)
{
dataGrid.Columns.Add(column);
}
}
if (e.OldItems != null)
{
foreach (DataGridColumn column in e.OldItems)
{
dataGrid.Columns.Remove(column);
}
}
}
#endregion
}
Listing 5. ComboBox with Default DataGrid Popup
The idea behind this control is to set the PopupContent to an instance of a DataGrid
by default. This DataGrid will have its IsReadOnly property set to true and AutoGenerateColumns
property to false. You could add dependency properties to the control if you
want some properties of the DataGrid be exposed. Then, we bind the ItemsSource
and SelectedItem properties of the DataGrid to the matching properties of the
ComboBox, thus removing the need to write this for every control. Next, we subscribe
to the SelectionChanged event of the DataGrid so that we can set the IsDropDownOpen
property to false. After this, we can already remove the IsEditingCustomer property
in our demo application. I have also added a Columns dependency property so that
we can add columns to the DataGrid.
<controls:ExtendedComboBoxDataGrid
Width="250"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ItemsSource="{StaticResource Customers}"
SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type
Window}}, Path=SelectedCustomer}">
<controls:ExtendedComboBoxDataGrid.Columns>
<toolkit:DataGridTextColumn
Header="Name"
Binding="{Binding Name}"/>
<toolkit:DataGridTextColumn
Header="Address"
Binding="{Binding Address}"/>
<toolkit:DataGridTextColumn
Header="Telephone No."
Binding="{Binding TelephoneNumber}"/>
</controls:ExtendedComboBoxDataGrid.Columns>
<controls:ExtendedComboBoxDataGrid.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Margin="4,0"/>
<TextBlock Text="{Binding Path=Address}" Margin="4,0"/>
<TextBlock Text="{Binding Path=TelephoneNumber}" Margin="4,0"/>
</StackPanel>
</DataTemplate>
</controls:ExtendedComboBoxDataGrid.ItemTemplate>
</controls:ExtendedComboBoxDataGrid>
Listing 6. Using the ComboBox with Default DataGrid Popup
The new control does the same thing as the previous one but as you can see, the XAML
code is cleaner.
Filtering DataGrid Items
Last thing that I want to discuss is that there are two control templates for the
ExtendedComboBox control in Generic.xaml. The default control template is what
we have been editing so far. The other one is used when the IsEditable property
is set to true. This template shows an ItemsPresenter inside a ScrollViewer.
As what we did in the default control template, we just need to replace the ScrollViewer
with the ContentControl and bind the Content property to the PopupContent property
of the ExtendedComboBox.

Figure 2. IsEditable Property Set to True
While we are at it, let’s add a simple auto-complete feature. When the text changes,
we will filter the items of the DataGrid by setting the Filter property of the
ItemCollection object (obtained from DataGrid.Items). To get the TextBox object
that contains the text, we need to override the OnApplyTemplate method of the
ExtendedComboBoxDataGrid control. Afterwards, we will subscribe to the TextChanged
event and change the Filter property at the event handler.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
TextBox editableTbx = GetTemplateChild("PART_EditableTextBox") as TextBox;
if (editableTbx != null)
{
editableTbx.TextChanged += EditableTbx_TextChanged;
}
}
private void EditableTbx_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox editableTbx = (TextBox)sender;
dataGrid.Items.Filter = new Predicate<object>(
o =>
{
return o.ToString().StartsWith(editableTbx.Text);
}
);
}
Listing 7. Filtering DataGrid Items
In the event handler, we compare the string representation of the DataGrid item with
the text the user typed in the TextBox. In our demo application, we need to override
the ToString method in the Customer class so that the Name is returned instead
of the type.
public class Customer
{
public string Name { get; set; }
public string Address { get; set; }
public string TelephoneNumber { get; set; }
public override string ToString()
{
return Name;
}
}
Listing 8. Override the ToString method
The following figure shows the demo application, this time with filtered items. Note
that the search criterion is not case-sensitive.

Figure 3. DataGrid with Filtered Items
You can download the Visual Studio 2008 solution here. Note that you need to have WPF Toolkit installed to run the application.
Popularity (9515 Views)
| Biography - Michael Detras |
.NET developer. Interested in WPF, Silverlight, and XNA.
My blog
My FAQs |
Article Discussion: WPF Datagrid as ComboBox Dropdown Part 2
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi,
Your control is amazing ....but......
but when I am trying to use it on the tab control ....it works odd...
only the combos present on the first tab works fine and the datagrid dropsdown but the combos on the other all tabs doesnt open up the datagrid jus shows the header. though the combo is loaded with list so when i click on up or down arrows it populates the field value but the drop down grid doesnt pop-up.
Can you please tell me what should be done to have the control (Datagrid as ComboBox Dropdown) work on tab control.
The tab control is on WPF user control.
Thanks
Shivangi
Michael Detras replied
to shivangi at Sunday, October 10, 2010 9:16 AM
Hi Shivangi,
Thanks for reading the article. Ok, I haven't tried putting the control in a tab control. I'll see what I can do.
Best regards,
Mike
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi Mike,
Thanks so much for looking into it.
I really appreciate your help.
Waiting for your reply.
Thanks
Shivang
Check this out
Michael Detras replied
to shivangi at Sunday, October 10, 2010 9:16 AM
Hi Shivang,
I've tried something out and this could probably solve the problem. I've added an event handler for the Loaded event in the constructor. Then, I've moved the data bindings from the constructor to the event handler.
Loaded += new RoutedEventHandler(ExtendedComboBoxDataGrid_Loaded);
private void ExtendedComboBoxDataGrid_Loaded(object sender, RoutedEventArgs e)
{
RelativeSource relativeSource = new RelativeSource(RelativeSourceMode.FindAncestor,
typeof(ExtendedComboBoxDataGrid), 1);
// Bind ItemsSource
Binding itemsSourceBinding = new Binding("ItemsSource");
itemsSourceBinding.RelativeSource = relativeSource;
dataGrid.SetBinding(DataGrid.ItemsSourceProperty, itemsSourceBinding);
// Bind SelectedItem
Binding selectedItemBinding = new Binding("SelectedItem");
selectedItemBinding.RelativeSource = relativeSource;
dataGrid.SetBinding(DataGrid.SelectedItemProperty, selectedItemBinding);
}
Kindly check if this works for you. I think (not sure though) that the TabControl defers the initialization of some properties of controls in other tab items so assigning the data bindings on the construtor won't work.
Thanks,
Mike
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hey Mike...
Thanks a lot..........
it works amazing now.....
Really appreciate ur help.
Thanksssssssssss a lotttttttt...
Thanks! I'm glad it worked for you.
Michael Detras replied
to shivangi at Sunday, October 10, 2010 9:16 AM
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi Mike...
Well i thought it was perfect but when i added more functionality to my form apart from design i realised that the code u sent
made the dropdown work on all tabs but since we added the event handler for the loaded event on the constructor...it doesnt hold the selected value in the dropdown on tab change event..
meaning
if i select a value in the dropdown on 1st tab and go to next tab and come bac to 1st tab the selected value get deleted as the load event is fired and new bindings occur...please Any idea or suggesstions to make it work
Thanks
Shivangi
Michael Detras replied
to shivangi at Sunday, October 10, 2010 9:16 AM
Hi Shivangi,
How about if you detach the event handler for the Loaded event after assigning the bindings? Will this work for you? Thanks.
Best regards,
Mike
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi Mike...
Thats what i want exactly but How to do that ??
Can you please brief me.
Thanks
Shi
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi Mike..
How do i detach the event handler ...can you please guide me
Thanks
Shivangi
Michael Detras replied
to shivangi at Sunday, October 10, 2010 9:16 AM
Hi Shivangi,
You can check if this works: Loaded -= ExtendedComboBoxDataGrid_Loaded; Please let me know of any problems. Thanks.
Regards,
Mike
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi
What i meant was where in code should i write the code line to detach the event handler.
I have to load the event handler on first time click of any tabs so that the combobox will get loaded with datagrid and dropdown opens up ..
so the situtation is for eg:
1. from is loaded tab 0 is active (load event is called)
2. click tab 1 the load event should be called else combobox will not get loaded with datagrid and dropdown wont open. so call load event
3. click tab 0 again the load event is called....this is where i dont want to called load event and detach it
4. again if go on tab 1 load event is called which shouldnt ...
so on first click of any tab it should be called then should detach it so where do i put this line of code.
Now what i have done is keepin a counter for each tab and checkin if its first click i set a static boolen variable and accordingly i load event else i dont ...this works but I am sure there has to be better solution..
coz i have lets say for now 10 tabs so i m declaring 10 variables and assingnin memory space n stuff....
So any better suggesstion???
Thanks
Shivangi
Michael Detras replied
to shivangi at Sunday, October 10, 2010 9:16 AM
Hi Shivangi,
Well, I was thinking of detaching the event handler in the ExtendedComboBoxDataGrid_Loaded method, at the end of the method. Will this work for you?
Thanks,
Mike
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi
Well I dont think so that will work coz as I said i have to load the event when first time clicking on of the tabs after form load as it doesnt get filled with datagrid for dropdown.
So i have to detach the event after i knw all comboboxes on all tabs hv been populated .
Did you get my point...M sorry m really bad at explaining.
Thanks
Shivangi
Michael Detras replied
to shivangi at Sunday, October 10, 2010 9:16 AM
Hi Shivangi,
Sorry for the late response. I was busy on a project. I've tried the code once again, removed the event handler for the Loaded event and put the data bindings on the OnApplyTemplate method.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
TextBox editableTbx = GetTemplateChild("PART_EditableTextBox") as TextBox;
if (editableTbx != null)
{
editableTbx.TextChanged += EditableTbx_TextChanged;
}
RelativeSource relativeSource = new RelativeSource(RelativeSourceMode.FindAncestor,
typeof(ExtendedComboBoxDataGrid), 1);
// Bind ItemsSource
Binding itemsSourceBinding = new Binding("ItemsSource");
itemsSourceBinding.RelativeSource = relativeSource;
dataGrid.SetBinding(DataGrid.ItemsSourceProperty, itemsSourceBinding);
// Bind SelectedItem
Binding selectedItemBinding = new Binding("SelectedItem");
selectedItemBinding.RelativeSource = relativeSource;
dataGrid.SetBinding(DataGrid.SelectedItemProperty, selectedItemBinding);
}
I think this works now. I hope this solves the problem.
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi Michael,
Thanks so much for the response...
this works great now.
I jus had a one more quick question.
I am workin with VS2010 and that has datagrid within it and need not use wpf toolkit datagrid.
But its not giving me required prooperties used here.
Did u try this code in VS2010 framework 4.0
Also wen i reworte all in framework 4.0 its not working fine it doesnt display the combox itself even if usinf wpf tookit
so i hvv tried it can I have that code.
Thanks
Shivangi
Jim replied
to shivangi at Sunday, October 10, 2010 9:16 AM
Any idea on how to change the drop down to a TreeView control instead of a DataGrid? I've spent the last couple days trying to figure out how to get this to work, but have only met with partial success.
shivangi replied
to shivangi at Sunday, October 10, 2010 9:16 AM
Hey...
I converted it to 4.0 and works fine but still using wpf toolkit..
thanks
Jim replied
to shivangi at Sunday, October 10, 2010 9:16 AM
It might be helpful to mention that the CustomControls' AssemblyInfo.cs needs a ThemeInfo attribute. I tried re-creating your project from scratch and couldn't figure out why my custom control wouldn't display. It took me about 4 hours to finally track down what the problem was.
Michael Detras replied
to shivangi at Sunday, October 10, 2010 9:16 AM
Hi Shivangi,
I still haven't tried upgrading the solution to .NET 4.0 and use the the built-in DataGrid there. I'll let you know if I'm successful in using .NET 4.0.
Hi Jim, thanks for the tip. I'll include this in the article later. Sorry, I was not able to read your message earlier. Your message was not forwarded to my e-mail since you replied to Shivangi.
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi
I had quick question.
How set the width for the dropdown grid. Its auto i think and expands if the contents widt expands..that is fine that is what i want but
It usually take the width of the combobox and lets say if there are only 2 columns there is an emplty space and looks like there are three columns instead of two.
Can you help on this one please.
How to jus show the exact columns and expand if the content width increases.
Thanks
Shivangi
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi...
I did it... had to change in generic.xaml.
Thanks
Shivangi
Kimberly replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hi Michael,
Thank you so much for this post. It's been really helpful to me in trying to create a similar 'enhanced' combo box control. I noticed that some of the usual functionality of a combo box is lost here: when the drop down is open, you cannot use the down arrow to move through the items in sequence. Pressing the key just once selects the top item and limits the list, because the filter is applied and (usually) only one item matches. In case it interests you or your readers, the following is one way to change the behavior:
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (IsDropDownOpen)
{
if (e.Key == Key.Down)
{
if (SelectedIndex < Items.Count)
{
_editableTbx.TextChanged -= EditableTbx_TextChanged;
SelectedIndex++;
_editableTbx.TextChanged += EditableTbx_TextChanged;
}
e.Handled = true;
return;
}
if (e.Key == Key.Up)
{
if (SelectedIndex >= 0)
{
_editableTbx.TextChanged -= EditableTextBox_TextChanged;
SelectedIndex--;
_editableTbx.TextChanged += EditableTextBox_TextChanged;
}
e.Handled = true;
return;
}
}
base.OnPreviewKeyDown(e);
}
By ignoring the TextChanged event handler, this prevents the filter from being automatically applied as soon as an item is selected. (The filter will then be out-of-sync with the text, but it can, for example, be reapplied when the drop down is closed or the next time it is opened.) This code requires assigning the TextBox to the class field _editableTbx in OnApplyTemplate() in order to reference it here, or else retrieving it again using Template.FindName(). As far as I can tell, none of the other functionality of the basic combo box depends on the up and down keys, so ignoring the base.OnPreviewKeyDown for those two keys seems to be harmless. A bit of a hack, but it makes keyboard-only use of this control a little bit easier.
Also helpful:
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (IsDropDownOpen && (SelectedItem != null))
{
_dataGrid.UpdateLayout();
_dataGrid.ScrollIntoView(SelectedItem);
}
}
Thanks again for a great article!
Kimberly
Thanks!
Michael Detras replied
to Kimberly at Sunday, October 10, 2010 9:16 AM
Hi Kimberly,
Thanks for reading the article and posting your code. I'm sure this will benefit other readers.
Michael
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
Hey...
I have one more question ....
When a particular record/value is selected in dropdown it doesn't scrolldown and show that value if the dropdown is big and has scrollbar .
I need to scrolldown to that row if its not visible when the dropdown opens up at first ..my requirement is as and when i type the text search comes up with full word and its also selected in the grid but if its not scrolled to that value and visible i hv to manully scroll down to see the selected value ...i need it to go as and when i type...is it possible...ny ideas...
Thanks in advance
Shivangi
Michael Detras replied
to shivangi at Sunday, October 10, 2010 9:16 AM
Hi Shivangi,
I think any ItemsControl has a ScrollIntoView method to show the selected item in the list. Kindly confirm if this is correct. You can use this method to programmatically scroll to the selected item. I wish I could update the article right now to address yours and others' comments right now but I am currently busy with other things. I hope this could be of help.
Thanks,
Mike
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
I tried making it work using
ContentPresenter itemcontainer = this.itemscontrol.ItemContainerGenerator.ContainerFromIndex(2) as ContentPresenter;
itemcontainer.BringIntoView();
But doesnt work....if you help me on this one...I really need it ..else i need to change whole application where m using this combobox....Please would really appreciate this help.
I have made few modifications as per my need..attached is my code
#region Constructors
public ExtendedComboBoxDataGrid()
{
dataGrid = new Microsoft.Windows.Controls.DataGrid();
dataGrid.IsReadOnly = true;
dataGrid.AutoGenerateColumns = false;
PopupContent = dataGrid;
Columns = new ObservableCollection<Microsoft.Windows.Controls.DataGridColumn>();
Columns.CollectionChanged += Columns_CollectionChanged;
}
#endregion
#region Overrides
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
TextBox editableTbx = GetTemplateChild("PART_EditableTextBox") as TextBox;
if (editableTbx != null)
{
IsDropDownOpen = true;
// editableTbx.TextChanged += EditableTbx_TextChanged;
}
RelativeSource relativeSource = new RelativeSource(RelativeSourceMode.FindAncestor,
typeof(ExtendedComboBoxDataGrid), 1);
// Bind ItemsSource
Binding itemsSourceBinding = new Binding("ItemsSource");
itemsSourceBinding.RelativeSource = relativeSource;
dataGrid.SetBinding(Microsoft.Windows.Controls.DataGrid.ItemsSourceProperty, itemsSourceBinding);
// Bind SelectedItem
Binding selectedItemBinding = new Binding("SelectedItem");
selectedItemBinding.RelativeSource = relativeSource;
dataGrid.SetBinding(Microsoft.Windows.Controls.DataGrid.SelectedItemProperty, selectedItemBinding);
IsDropDownOpen = false;
}
#endregion
#region Private Methods
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
IsDropDownOpen = false;
}
private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Microsoft.Windows.Controls.DataGridColumn column in e.NewItems)
{
dataGrid.Columns.Add(column);
}
}
if (e.OldItems != null)
{
foreach (Microsoft.Windows.Controls.DataGridColumn column in e.OldItems)
{
dataGrid.Columns.Remove(column);
}
}
}
private void EditableTbx_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox editableTbx = (TextBox)sender;
dataGrid.Items.Filter = new Predicate<object>(
o =>
{
//IsDropDownOpen = true;
return o.ToString().StartsWith(editableTbx.Text);
}
);
}
#endregion
Thanks
Shivangi
shivangi replied
to Michael Detras at Sunday, October 10, 2010 9:16 AM
I tried making it work using
ContentPresenter itemcontainer = this.itemscontrol.ItemContainerGenerator.ContainerFromIndex(2) as ContentPresenter;
itemcontainer.BringIntoView();
But doesnt work....if you help me on this one...I really need it ..else i need to change whole application where m using this combobox....Please would really appreciate this help.
I have made few modifications as per my need..attached is my code
#region Constructors
public ExtendedComboBoxDataGrid()
{
dataGrid = new Microsoft.Windows.Controls.DataGrid();
dataGrid.IsReadOnly = true;
dataGrid.AutoGenerateColumns = false;
PopupContent = dataGrid;
Columns = new ObservableCollection<Microsoft.Windows.Controls.DataGridColumn>();
Columns.CollectionChanged += Columns_CollectionChanged;
}
#endregion
#region Overrides
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
TextBox editableTbx = GetTemplateChild("PART_EditableTextBox") as TextBox;
if (editableTbx != null)
{
IsDropDownOpen = true;
// editableTbx.TextChanged += EditableTbx_TextChanged;
}
RelativeSource relativeSource = new RelativeSource(RelativeSourceMode.FindAncestor,
typeof(ExtendedComboBoxDataGrid), 1);
// Bind ItemsSource
Binding itemsSourceBinding = new Binding("ItemsSource");
itemsSourceBinding.RelativeSource = relativeSource;
dataGrid.SetBinding(Microsoft.Windows.Controls.DataGrid.ItemsSourceProperty, itemsSourceBinding);
// Bind SelectedItem
Binding selectedItemBinding = new Binding("SelectedItem");
selectedItemBinding.RelativeSource = relativeSource;
dataGrid.SetBinding(Microsoft.Windows.Controls.DataGrid.SelectedItemProperty, selectedItemBinding);
IsDropDownOpen = false;
}
#endregion
#region Private Methods
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
IsDropDownOpen = false;
}
private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Microsoft.Windows.Controls.DataGridColumn column in e.NewItems)
{
dataGrid.Columns.Add(column);
}
}
if (e.OldItems != null)
{
foreach (Microsoft.Windows.Controls.DataGridColumn column in e.OldItems)
{
dataGrid.Columns.Remove(column);
}
}
}
private void EditableTbx_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox editableTbx = (TextBox)sender;
dataGrid.Items.Filter = new Predicate<object>(
o =>
{
//IsDropDownOpen = true;
return o.ToString().StartsWith(editableTbx.Text);
}
);
}
#endregion
Thanks
Shivangi
Dale replied
to shivangi at Sunday, October 10, 2010 9:16 AM
If you have say 100 items in the grid and you want to initialize the current row and scroll to the selected index
ComboBox1.SelectedIndex = 21;
ScrollIntoView(ComboBox1.SelectedItem);
I can't seem to get the initialization to work => it works great if the initial selected item is not defined. If user selects the row from the grid after it is dropped down and re-opens the dropdown it works great.
shivangi replied
to Dale at Sunday, October 10, 2010 9:16 AM
HI,
Do you have the same for Silverlight too.
A multiple combobox for silverlight.
I cannot reference this in Silverlight project , So have you created one for SIlverLight too?
Can you share it please.
Shivangi
sn reddy replied
to shivangi at Sunday, October 10, 2010 9:16 AM
http://sharepointsolution2010.blogspot.com/2012/01/situation-silverlight-datagrid-to-given.html