views:

37

answers:

1

I have a ComboBox that is bound to an ObservableCollection of custom UserControls. Each user control has a Tag value set and the ComboBox' DisplayMemberPath is set to "Tag". This correctly displays the Tag of each UserControl in the drop down list when the ComboBox is clicked, however when an item in the list is selected and the drop down list is closed, the ComboBox displays nothing in the button.

If I swap out a UserControl for a standard WPF control such as a TextBox, then it correctly displays the Tag value of the selected item, so it is something related to binding to a UserControl vs a standard WPF control. Also, if I set the IsEditable to True, then the editable TextBox displays the Tag correctly, but I don't want the text to be editable.

How do I get the Selected item to display when the ComboBox is not expanded?

Here is some sample code that replicates the issue:

(Note: The sample code is taken out of the context of the application it is running in so it looks a bit weird in what it is trying to do, but it still results in the same symptoms).

MyUC.xaml

<UserControl x:Class="ComboboxTest.MyUC"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
    <Grid>
        <TextBox />
    </Grid>
</UserControl>

Window1.xaml

<Window x:Class="ComboboxTest.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ComboboxTest"
        Title="Window1" Height="300" Width="300"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
    <StackPanel Name="ControlsHolder">
        <TextBox Tag="Box 1" Text="This is in Box 1" />
        <TextBox Tag="Box 2" Text="This is in Box 2" />
        <local:MyUC Tag="UC 1" />
        <local:MyUC Tag="UC 2" />
    </StackPanel>
    <Grid>
        <ComboBox Grid.Column="1" 
              Margin="5,0" 
              Name="MyComboBox" 
              ItemsSource="{Binding MyControls}"
              DisplayMemberPath="Tag" 
              MinWidth="120"/>
    </Grid>
</StackPanel>

Window1.cs

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace ComboboxTest
{
    public partial class Window1 : Window, INotifyPropertyChanged
    {
        ObservableCollection<MyUC> myControls = new ObservableCollection<MyUC>();

        public Window1()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Window1_Loaded);
        }

        void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            myControls.Clear();

            foreach (UIElement uiElement in this.ControlsHolder.Children)
            {
                MyUC tb = uiElement as MyUC;

                if (tb != null)
                {
                    myControls.Add(tb);
                }
            }

            RaisePropertyChanged("MyControls");
        }

        public ObservableCollection<MyUC> MyControls
        {
            get
            {
                return this.myControls;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string p)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(p));
            }
        }
    }
}

This app appears as: ComboBox with Drop Down Visible

And when "UC 2" is selected it appears as: ComboBox with selected item not visible

A: 

Binding a list of UIElements is not a good idea. Try using a wrapper class :

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(Window1_Loaded);
        MyComboBox.ItemsSource = MyControls;
    }

    ObservableCollection<Wrapper> myControls = new ObservableCollection<Wrapper>();

    void Window1_Loaded(object sender, RoutedEventArgs e)
    {
        myControls.Clear();

        foreach (UIElement uiElement in this.ControlsHolder.Children)
        {
            MyUC tb = uiElement as MyUC;

            if (tb != null)
            {
                myControls.Add(new Wrapper(tb));
            }
        }

        RaisePropertyChanged("MyControls");
    }

    public ObservableCollection<Wrapper> MyControls
    {
        get
        {
            return this.myControls;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged(string p)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(p));
        }
    }
}

public class Wrapper
{
    public UserControl Control { get; protected set; }

    public Wrapper(UserControl control)
    {
        Control = control;
    }

    public Object Tag
    {
        get { return Control.Tag; }
    }
}
decyclone
Thanks decyclone, that did the trick. But do you have an explanation as to why wrapping it inside another class works, and why binding to a UIElement is a bad idea?
Casey
By adding UserControls to ItemsSource, they don't become part of the VisualTree of the ComboBox. Each object in ItemsSource gets wrapped with a ComboBoxItem. That is the reason why they don't get displayed pretty well in the ComboBox. While using ItemsSource binding using classes instead of UIElements is recommended.
decyclone