Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ComboBox ItemTemplate only working in dropdown

Tags:

combobox

wpf

I am trying to show a ComboBox whose ItemsSource is a collection of Controls (it is part of a PropertyGrid, the ComboBox should display the names of the controls, and the user should be able to select one of the controls). Here is an extremely simplified reproduction of the problem:

<ComboBox ItemsSource="{Binding GroupBoxes}" SelectedValue="{Binding SelectedGroupBox}">
  <ComboBox.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding Name}"/>
    </DataTemplate>
  </ComboBox.ItemTemplate>          
</ComboBox>

GroupBoxes and SelectedGroupBox are DependencyProperties of type ObservableCollection and GroupBox.

The Bindings work - the control names are displayed in the ComboBox-DropDown, and if I select a different item I can see that the SelectedGroupBox property is updated correctly. The problem: the selected item is never displayed in the ComboBox. Setting the SelectedGroupBox property from code also works as expected - the ComboBox raises SelectionChanged and its SelectedValue is correct, but it still doesn't display the current value.

If I do the exact same thing with any other type of class, everything works as expected.

Searching for an answer I came across many posts from people having similar sounding problems, but almost all of them were Binding proplems which is not the case here.

Edit:

To simplify trying it out, here's the code behind. Just drop the above XAML in a new Window, and the code below in the code behind.

public MainWindow() {
    InitializeComponent();
    this.DataContext = this;
    this.GroupBoxes = new ObservableCollection<GroupBox>();
    this.GroupBoxes.Add(new GroupBox() { Name = "AAA", Header = "AAA", Height = 100, Background = Brushes.Purple });
    this.GroupBoxes.Add(new GroupBox() { Name = "BBB", Header = "BBB", Height = 100, Background = Brushes.Purple });
    this.GroupBoxes.Add(new GroupBox() { Name = "CCC", Header = "CCC", Height = 100, Background = Brushes.Purple });
    this.GroupBoxes.Add(new GroupBox() { Name = "DDD", Header = "DDD", Height = 100, Background = Brushes.Purple });
    this.GroupBoxes.Add(new GroupBox() { Name = "EEE", Header = "EEE", Height = 100, Background = Brushes.Purple });
}

#region GroupBoxesProperty

public static readonly DependencyProperty GroupBoxesProperty = DependencyProperty.Register(
    "GroupBoxes", typeof(ObservableCollection<GroupBox>), typeof(MainWindow)
);

public ObservableCollection<GroupBox> GroupBoxes {
    get { return (ObservableCollection<GroupBox>)GetValue(GroupBoxesProperty); }
    set { SetValue(GroupBoxesProperty, value); }
}

#endregion

#region SelectedGroupBoxProperty

public static readonly DependencyProperty SelectedGroupBoxProperty = DependencyProperty.Register(
    "SelectedGroupBox", typeof(GroupBox), typeof(MainWindow),
    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (s, e) => (s as MainWindow).OnSelectedGroupBoxChanged())
);

public GroupBox SelectedGroupBox {
    get { return (GroupBox)GetValue(SelectedGroupBoxProperty); }
    set { SetValue(SelectedGroupBoxProperty, value); }
}

void OnSelectedGroupBoxChanged() {
    Console.WriteLine("selection is now " + this.SelectedGroupBox.Name);
}

#endregion
like image 584
wilford Avatar asked Nov 23 '11 12:11

wilford


3 Answers

The ComboBox, for some very complex reasons exposes a read-only property called SelectionBoxItem. The content presenter in the ComboBox template binds on this property. It is the SelectionBoxItem that exposes the string representation of non-UI elements allowing you to see the selected value. The use of this property is what prevents the content presenter from using data templates. This is why the template applies to the drop down but not the selected item. Here is the part of the default ComboBox template causing the issue:

<ContentPresenter IsHitTestVisible="false"
    Margin="8,1,1,1"
    Content="{TemplateBinding SelectionBoxItem}"
    ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
    ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>

You can however create your own ComboBox style that overrides the default ContentPresenter and uses the SelectedItem instead of SelectionBoxItem and ItemTemplate instead of SelectionItemBoxTemplate. This will resolve the issue.

like image 159
icirellik Avatar answered Nov 04 '22 00:11

icirellik


Use DisplayMemberPath instead of binding to name:

 <Combobox DisplayMemberPath="Name" ... />

The reason for the behaviour you see probably is that you need to set another property for selected item's template: http://msdn.microsoft.com/en-us/library/system.windows.controls.combobox.selectionboxitemtemplate.aspx


Update: I've written my answer without checking your code, sorry for that. Now I've read your code and noticed that you're binding SelectedValue property. I do no think this is the best property to bind in your case, usually property SelectedItem should be used. I remember that I never had to do anything with SelectionBoxItem stuff mentioned in other answer, that's probably because SelectedValue and SelectedItem properties behave differently and I tend to use SelectedItem whenever I can. In your case I would use it too.

like image 31
Snowbear Avatar answered Nov 04 '22 01:11

Snowbear


This link provides a solution and a demo. The trick is to use a DataTemplateSelector instead of DataTemplate in cases where it desirable to control the apperance of the SelectedItem, e.g:

<ComboBox ItemsSource="{Binding GroupBoxes}" SelectedValue="{Binding SelectedGroupBox}">
    <ComboBox.ItemTemplateSelector>
            <ts:ComboBoxItemTemplateSelector>
                <ts:ComboBoxItemTemplateSelector.SelectedTemplate>
                    <DataTemplate>
                    <TextBlock Text="{Binding Name}"/>
                    </DataTemplate>
                </ts:ComboBoxItemTemplateSelector.SelectedTemplate>                  
            </ts:ComboBoxItemTemplateSelector>
        </ComboBox.ItemTemplateSelector>     
</ComboBox>

public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate SelectedTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        return SelectedTemplate;     
    }
}
like image 1
Declan Taylor Avatar answered Nov 03 '22 23:11

Declan Taylor