Odeslat dotazOdeslat dotaz
 

OdpovědětComboBox that supports the "empty value" item

  • 29. září 2009 13:46Geert van Horrik Uživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaile
     Obsahuje kód
    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!

Odpovědi

  • 29. září 2009 14:19Bigsby Uživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaile
     OdpovědětObsahuje kód
    (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
  • 3. listopadu 2009 19:00Geert van Horrik Uživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaile
     Odpovědět
    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!

Všechny reakce

  • 29. září 2009 14:19Bigsby Uživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaile
     OdpovědětObsahuje kód
    (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
  • 30. září 2009 7:49Geert van Horrik Uživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaile
     
    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!
  • 3. listopadu 2009 16:00cmclernon Uživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaile
     

    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,

    Colm

  • 3. listopadu 2009 17:12Bigsby Uživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaile
     Obsahuje kód

    Bind 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
  • 3. listopadu 2009 19:00Geert van Horrik Uživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaile
     Odpovědět
    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!
  • 4. listopadu 2009 15:48cmclernon Uživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaileUživatelské medaile
     
    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