Search code examples
c#.netwpfxamlcombobox

Why is this ComboBox rendering the SelectedItem directly as content, instead of using the ItemTemplate?


I have a ComboBox displaying a list of items. I've created a converter to map from this object type to a string value.

<ComboBox ItemsSource="{Binding SelectedDrawing.Icons}" Name="TrackElementSelector" SelectedItem="{Binding SelectedElement}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=., Converter={StaticResource LayoutElementToStringConverter}}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

The problem is that when I select/click a value in the ComboBox, the ComboBox renders the SelectedItem directly (it's a UserControl defined elsewhere in my project). For example, imagine seeing a literal Button after clicking the string element "Button" on the ComboBox. I do not want this behavior.

Why isn't the ItemTemplate being used here? It seems like the ItemTemplate is only used until a value is selected.


I've discovered another unexpected result. When I drop down the ComboBox to make a selection, the instances where the controls appear in other places of my application disappear. I'm wondering if there's something more complex going on due to stuff in my codebase, or if this is all consequence of trying to display UserControl objects inside a ComboBox.


Solution

  • You discovered an interesting behavior of the the ComboBox control. When binding items, how the items are presented sometimes depends on their type. For example, if you bind items that are already of type ComboBoxItem, it will not create additional containers, since that would be redundant. This is a common design decision for other ItemsControls as well.

    Why Item Template is Ignored

    However, your issue originates from a different special treatment of the selected item in ComboBox regarding ContentControls. If the selected item is of this type or any of its direct or indirect derivatives (like UserControl), ComboBox will use its Content, ContentTemplate and ContentStringFormat, instead of the ItemTemplate or ItemStringFormat defined for the items on the ComboBox itself, thus ignoring the ItemTemplate.

    Implementation Details

    You can see this in the implementation of ComboBox within the method UpdateSelectionBoxItem:

    // if Items contains an explicit ContentControl, use its content instead
    // (this handles the case of ComboBoxItem)
    ContentControl contentControl = item as ContentControl;
    
    if (contentControl != null)
    {
        item = contentControl.Content;
        itemTemplate = contentControl.ContentTemplate;
        stringFormat = contentControl.ContentStringFormat;
    }
    

    I do not know why it was implemented like this, but the original comment suggests that this might be intended to handle the case where the bound items are already ComboBoxItem. Maybe this behavior was not intented, but accidential.

    Solutions

    If you wanted to change this behavior, you have these options:

    1. Create your own custom implementation of ComboBox.

    2. Add the item template to UserControl as content template as well.

      Although it works with the given implementation, it is sort of a hack.

    3. Create a custom control template which ignores the special handling.

      You would create a copy of the default style and control template and adapt its ContentPresenter. By default it is bound to the special SelectionBoxItemTemplate property. You could ignore it by binding to the ItemTemplate property instead.

      <ContentPresenter x:Name="contentPresenter"
                        Content="{TemplateBinding SelectedItem}"
                        ContentTemplate="{TemplateBinding ItemTemplate}"
                        ...>
      
    4. Do not use a ContentControl or derived type for items, but use data templating with data items as source instead, as it is intended, see Data Templating Overview.

      You should not bind UI elements as items source of the ComboBox. Instead, bind the data encapsulated in a model instead and provide a suitable data template to display it. The data could be an Icon class with a property Path or even simply a string representing the path.

      The data template could reuse your UserControl type to display it (if it is correctly implemented to bind data using its data context). This will result in a better design and will most likely eliminate the need for a value converter, too. Futhermore, it will solve your second issue automatically, the disappearing controls.

      I strongly recommend you to use this option, since it does not require any customization of framework elements and is much easier to do and maintain.

    Why Controls Dissappear

    In WPF UI controls are attached to the so-called visual tree, which is a hierarchical object graph representation of your user interface. In this graph, each UI element instance can only have a single parent. Consequently, if you "use" an existing control in a different place, it is effectively reassigned to the corresponding parent be it in the same or a different visual tree (e.g. another window) and "disappears" from the original place.