Microsoft Developer Network >
Página Inicial dos Fóruns
>
Windows Presentation Foundation (WPF)
>
ComboBox that supports the "empty value" item
ComboBox that supports the "empty value" item
- I want a combobox where a user can make a selection of a list of items, including a default one (none). I don't want the developer that uses the combobox to add the empty item to the combobox manually.
Therefore, I tried something like this (sorry for the long code). I will first explain my problem further, the the code is at the bottom of this post.
What I actually do is bind the itemssource to a binding or dynamic resource. Then, when the ItemsSource changes, I store the original items source and create my own with the none item. This works great the first time. However, then I loose the connection with the DynamicResource and thus I am not aware of future updates. I have used reflector to see what happens under the hood, but as always, the part I need is internal... Is there a better way to implement my requirements or is there a way I can subscribe to changes of a dynamic resource.
/// <summary> /// Implements an advanced combobox with the following features: /// /// * Automatically selects the value in case that there is only one /// value available in the items source. /// * Supports the MaxLength property. /// </summary> public class ComboBoxEx : System.Windows.Controls.ComboBox { #region Variables private ComboBoxItem _emptyItem = null; private IEnumerable _originalItemsSource = null; private ObservableCollection<object> _customItemsSource = new ObservableCollection<object>(); #endregion #region Constructor & destructor /// <summary> /// Initializes a new instance of this control. /// </summary> public ComboBoxEx() { // Set default values _emptyItem = new ComboBoxItem(); } #endregion #region Properties /// <summary> /// Gets or sets whether the control is currently changing the items source. /// </summary> private bool IsChangingItemsSource { get; set; } /// <summary> /// Wrapper for the EmptyValueText dependency property. /// </summary> public string EmptyValueText { get { return (string)GetValue(EmptyValueTextProperty); } set { SetValue(EmptyValueTextProperty, value); } } // Using a DependencyProperty as the backing store for EmptyValueText. This enables animation, styling, binding, etc... public static readonly DependencyProperty EmptyValueTextProperty = DependencyProperty.Register("EmptyValueText", typeof(string), typeof(ComboBoxEx), new UIPropertyMetadata(Properties.Resources.Empty, new PropertyChangedCallback(EmptyValueText_Changed))); #endregion #region Methods /// <summary> /// Invoked when the EmptyValueText dependency property has changed. /// </summary> /// <param name="sender">The object that contains the dependency property.</param> /// <param name="e">The event data.</param> private static void EmptyValueText_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e) { // Get typed sender ComboBoxEx typedSender = sender as ComboBoxEx; if (typedSender != null) { // Update content typedSender._emptyItem.Content = e.NewValue; } } /// <summary> /// Invoked when the original items source has changed. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">EventArgs.</param> private void OriginalItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // Update items source UpdateItemsSource(); } /// <summary> /// Invoked when the ItemsSource-property has changed. /// </summary> /// <param name="oldValue">Old value.</param> /// <param name="newValue">New value.</param> protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue) { // Call base base.OnItemsSourceChanged(oldValue, newValue); // Are we already changing? if (IsChangingItemsSource) return; // We are changing IsChangingItemsSource = true; // Store original itemssource _originalItemsSource = newValue; if (_originalItemsSource is INotifyCollectionChanged) { // Subscribe to changes ((INotifyCollectionChanged)_originalItemsSource).CollectionChanged += new NotifyCollectionChangedEventHandler(OriginalItemsSource_CollectionChanged); } // Update items source UpdateItemsSource(); // Not changing any longer IsChangingItemsSource = false; } /// <summary> /// Updates the itemssource. /// </summary> private void UpdateItemsSource() { // Clear custom items source _customItemsSource.Clear(); // Enable empty value if required _customItemsSource.Add(_emptyItem); // Fill custom items source if (_originalItemsSource != null) { foreach (object obj in _originalItemsSource) { _customItemsSource.Add(obj); } } // Update itemssource ItemsSource = _customItemsSource; } #endregion }
Geert van Horrik - CatenaLogic
Visit my blog: http://blog.catenalogic.com
Looking for a way to deploy your updates to all your clients? Try Updater!
Respostas
- (Please don't copy code directly from VS. Use '</>' icon to insert code snippet.)
You don't need all that. Do you need more than this:
<Window x:Class="MSDNTest.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MSDNTest" Width="500" Height="500" > <Window.Resources> <Style TargetType="{x:Type local:EmptyItemComboBox}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:EmptyItemComboBox}"> <ComboBox> <ComboBox.Resources> <CollectionViewSource x:Key="list" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:EmptyItemComboBox}}, Path=ItemsSource}"/> </ComboBox.Resources> <ComboBox.ItemsSource> <CompositeCollection> <ComboBoxItem Content="{TemplateBinding EmptyItemText}"/> <CollectionContainer Collection="{Binding Source={StaticResource list}}"/> </CompositeCollection> </ComboBox.ItemsSource> </ComboBox> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <StackPanel> <local:EmptyItemComboBox x:Name="combo1" EmptyItemText="Choose one"/> <local:EmptyItemComboBox x:Name="combo2" EmptyItemText="What ever"/> </StackPanel> </Window><br/>
using System.Collections.Generic; using System.Windows; using System.Windows.Controls; namespace MSDNTest { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); var list1 = new List<MyClass1>(); for (var i = 1; i <= 5; i++) list1.Add(new MyClass1 { Name = "My Class1 " + i }); combo1.ItemsSource = list1; var list2 = new List<MyClass1>(); for (var i = 1; i <= 5; i++) list2.Add(new MyClass1 { Name = "My Class2 " + i }); combo2.ItemsSource = list2; } } public class EmptyItemComboBox : ComboBox { public static readonly DependencyProperty EmptyItemTextProperty = DependencyProperty.Register("EmptyItemText", typeof(string), typeof(EmptyItemComboBox)); public string EmptyItemText { get { return (string)GetValue(EmptyItemTextProperty); } set { SetValue(EmptyItemTextProperty, value); } } } public class MyClass1 { public string Name { get; set; } public override string ToString() { return Name; } } public class MyClass2 { public string Name { get; set; } public override string ToString() { return Name; } } }
Bigsby, Lisboa, Portugal
O que for, quando for, é que será o que é...
Wenn ist das Nunstruck git und Slotermeyer? Ja! ... Beiherhund das Oder die Flipperwaldt gersput!
http://bigsby.eu- Marcado como RespostaGeert van Horrik quarta-feira, 30 de setembro de 2009 7:49
- The problem is that TemplateBinding is a oneway binding, and that is not sufficient in all cases. I have created a blog post for the control I created.
Geert van Horrik - CatenaLogic
Visit my blog: http://blog.catenalogic.com
Looking for a way to deploy your updates to all your clients? Try Updater!- Marcado como RespostaGeert van Horrik quarta-feira, 4 de novembro de 2009 19:27
Todas as Respostas
- (Please don't copy code directly from VS. Use '</>' icon to insert code snippet.)
You don't need all that. Do you need more than this:
<Window x:Class="MSDNTest.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MSDNTest" Width="500" Height="500" > <Window.Resources> <Style TargetType="{x:Type local:EmptyItemComboBox}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:EmptyItemComboBox}"> <ComboBox> <ComboBox.Resources> <CollectionViewSource x:Key="list" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:EmptyItemComboBox}}, Path=ItemsSource}"/> </ComboBox.Resources> <ComboBox.ItemsSource> <CompositeCollection> <ComboBoxItem Content="{TemplateBinding EmptyItemText}"/> <CollectionContainer Collection="{Binding Source={StaticResource list}}"/> </CompositeCollection> </ComboBox.ItemsSource> </ComboBox> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <StackPanel> <local:EmptyItemComboBox x:Name="combo1" EmptyItemText="Choose one"/> <local:EmptyItemComboBox x:Name="combo2" EmptyItemText="What ever"/> </StackPanel> </Window><br/>
using System.Collections.Generic; using System.Windows; using System.Windows.Controls; namespace MSDNTest { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); var list1 = new List<MyClass1>(); for (var i = 1; i <= 5; i++) list1.Add(new MyClass1 { Name = "My Class1 " + i }); combo1.ItemsSource = list1; var list2 = new List<MyClass1>(); for (var i = 1; i <= 5; i++) list2.Add(new MyClass1 { Name = "My Class2 " + i }); combo2.ItemsSource = list2; } } public class EmptyItemComboBox : ComboBox { public static readonly DependencyProperty EmptyItemTextProperty = DependencyProperty.Register("EmptyItemText", typeof(string), typeof(EmptyItemComboBox)); public string EmptyItemText { get { return (string)GetValue(EmptyItemTextProperty); } set { SetValue(EmptyItemTextProperty, value); } } } public class MyClass1 { public string Name { get; set; } public override string ToString() { return Name; } } public class MyClass2 { public string Name { get; set; } public override string ToString() { return Name; } } }
Bigsby, Lisboa, Portugal
O que for, quando for, é que será o que é...
Wenn ist das Nunstruck git und Slotermeyer? Ja! ... Beiherhund das Oder die Flipperwaldt gersput!
http://bigsby.eu- Marcado como RespostaGeert van Horrik quarta-feira, 30 de setembro de 2009 7:49
- I did use the "Insert code block" tool button. Apparently it can't handle comments, so sorry for the messed up code.
But, back on topic, thanks for your help :)
Geert van Horrik - CatenaLogic
Visit my blog: http://blog.catenalogic.com
Looking for a way to deploy your updates to all your clients? Try Updater! Hi,
Great code. I'm having a problem getting the SelectedValue and SelectedItem. It seems the ItemsSource property is being changed to IEnumerable of objects from a Dictionary and no longer recognises the Key and Value property names that I'm supplying in DisplayMemberPath and SelectedValuePath.
Any ideas?
Thanks,
ColmBind inner ComboBox DisplayMemberPath and SelectedValuePath to TemplateBinding. Something like so:
<ControlTemplate ... > <ComboBox DisplayMemberPath="{TemplateBinding DisplayMemberPath}" SelectedValuePath="{TemplateBinding SelectedValuePath}"> </ComboBox> </ControlTemplate>
Bigsby, Lisboa, Portugal
O que for, quando for, é que será o que é...
Wenn ist das Nunstruck git und Slotermeyer? Ja! ... Beiherhund das Oder die Flipperwaldt gersput!
http://bigsby.eu- The problem is that TemplateBinding is a oneway binding, and that is not sufficient in all cases. I have created a blog post for the control I created.
Geert van Horrik - CatenaLogic
Visit my blog: http://blog.catenalogic.com
Looking for a way to deploy your updates to all your clients? Try Updater!- Marcado como RespostaGeert van Horrik quarta-feira, 4 de novembro de 2009 19:27
- Thanks for the replies. I'll have a look at that blog - been pulled onto another project so will let you know if I get sorted whenever I get back to this...if ever. :)
Thanks again.
Colm

