开发者

WPF: How to customize SelectionBoxItem in ComboBox

I want to display a开发者_StackOverflow中文版 custom template/item as selected item in ComboBox (this item does not actually exist in the list of items and is updated differently). This does not even needs to be an item, just providing a custom view would work.

How can I do this while staying within current ComboBox theme (so no ControlTemplate replacement possible)? As far as I see, all of SelectionBox* properties are not editable and internally ComboBox uses unnamed ContentPresenter.


I would do it like this:

<Window.Resources>

  <DataTemplate x:Key="NormalItemTemplate" ...>
    ...
  </DataTemplate>

  <DataTemplate x:Key="SelectionBoxTemplate" ...>
    ...
  </DataTemplate>

  <DataTemplate x:Key="CombinedTemplate">
    <ContentPresenter x:Name="Presenter"
       Content="{Binding}"
       ContentTemplate="{StaticResource NormalItemTemplate}" />
    <DataTemplate.Triggers>
      <DataTrigger
        Binding="{Binding RelativeSource={RelativeSource FindAncestor,ComboBoxItem,1}}"
        Value="{x:Null}">
        <Setter TargetName="Presenter" Property="ContentTemplate"
                Value="{StaticResource SelectionBoxTemplate}" />
      </DataTrigger>
    </DataTemplate.Triggers>
  </DataTemplate>

</Window.Resources>

...

<ComboBox
  ItemTemplate="{StaticResource CombinedTemplate}"
  ItemsSource="..."
  ... />

The reason this works is that CombinedTemplate normally just uses NormalItemTemplate to present its data, but if there is no ComboBoxItem ancestor it assumes it is in the selection box so it uses SelectionBoxTemplate.

Note that the three DataTemplates could be included in any level of ResourceDictionary (not just at the Window level) or even directly within the ComboBox, depending on your preference.


If I have this straight, you want a control that has something arbitrary displayed along with a drop-down button that displays a list of items with checkboxes next to them?

I wouldn't even bother trying to restyle a ComboBox to achieve this. The problem is that ComboBox is more specialized down a different path than what you need. If you look at the ComboBox ControlTemplate Example, you'll see that it simply uses a Popup control to display the list of possible values.

You can take pieces of that template as guidance to creating a UserControl that is easier to understand and better provides what you want. You'll even be able to add a SelectedItems property and such that ComboBox doesn't provide.

An example of what I mean by guidance: the Popup has an IsOpen property. In the control template, it's set to {TemplateBinding IsDropDownOpen}, which means that the ComboBox class has an IsDropDownOpen property that is changed in order to control the expand/collapse of the Popup.


Alexey Mitev's comment on Ray Burns' answer inspired me to write the following reasonably short utility class, which I now use in all my WPF projects:

public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
    public List<DataTemplate> SelectedItemTemplates { get; } = new List<DataTemplate>();
    public List<DataTemplate> DropDownItemTemplates { get; } = new List<DataTemplate>();

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        return GetVisualParent<ComboBoxItem>(container) == null
            ? ChooseFrom(SelectedItemTemplates, item)
            : ChooseFrom(DropDownItemTemplates, item);
    }

    private static DataTemplate ChooseFrom(IEnumerable<DataTemplate> templates, object item)
    {
        if (item == null)
            return null;
        var targetType = item.GetType();
        return templates.FirstOrDefault(t => (t.DataType as Type) == targetType);
    }

    private static T GetVisualParent<T>(DependencyObject child) where T : Visual
    {
        while (child != null && !(child is T))
            child = VisualTreeHelper.GetParent(child);
        return child as T;
    }
}

With that in the toolbox, it's possible to write XAML like this:

<UserControl.Resources>
     <DataTemplate x:Key="SelectedItemTemplateForInt" DataType="{x:Type system:Int32}">
         <!-- ... -->
     </DataTemplate>

     <DataTemplate x:Key="SelectedItemTemplateForDouble" DataType="{x:Type system:Double}">
         <!-- ... -->
     </DataTemplate>

     <DataTemplate x:Key="DropDownItemTemplateForInt" DataType="{x:Type system:Int32}">
         <!-- ... -->
     </DataTemplate>

     <DataTemplate x:Key="DropDownItemTemplateForDouble" DataType="{x:Type system:Double}">
         <!-- ... -->
     </DataTemplate>
</UserControl.Resources>

<ComboBox>
    <ComboBox.ItemTemplateSelector>
        <local:ComboBoxItemTemplateSelector>
            <local:ComboBoxItemTemplateSelector.SelectedItemTemplates>
                <StaticResource ResourceKey="SelectedItemTemplateForInt" />
                <StaticResource ResourceKey="SelectedItemTemplateForDouble" />
            </local:ComboBoxItemTemplateSelector.SelectedItemTemplates>

            <local:ComboBoxItemTemplateSelector.DropDownItemTemplates>
                <StaticResource ResourceKey="DropDownItemTemplateForInt" />
                <StaticResource ResourceKey="DropDownItemTemplateForDouble" />
            </local:ComboBoxItemTemplateSelector.DropDownItemTemplates>
        </local:ComboBoxItemTemplateSelector>
    </ComboBox.ItemTemplateSelector>
</ComboBox>


You need to look into Triggers and Styles. You might also want to look into some of my older questions here on StackOverflow that helped me conquer these problems:

  • Displaying Content only when ListViewItem is Selected
  • Using Styles in Windows Presentation Foundation
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜