我可以为 WPF ComboBox 中的选定项目使用与下拉部分中的项目不同的模板吗? [英] Can I use a different Template for the selected item in a WPF ComboBox than for the items in the dropdown part?

查看:11
本文介绍了我可以为 WPF ComboBox 中的选定项目使用与下拉部分中的项目不同的模板吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 WPF 组合框,里面装满了 Customer 对象.我有一个数据模板:

I have a WPF Combobox which is filled with, say, Customer objects. I have a DataTemplate:

<DataTemplate DataType="{x:Type MyAssembly:Customer}">
    <StackPanel>
        <TextBlock Text="{Binding Name}" />
        <TextBlock Text="{Binding Address}" />
    </StackPanel>
</DataTemplate>

这样,当我打开 ComboBox 时,我可以看到不同的客户以及他们的姓名和地址.

This way, when I open my ComboBox, I can see the different Customers with their Name and, below that, the Address.

但是当我选择一个客户时,我只想在 ComboBox 中显示名称.类似的东西:

But when I select a Customer, I only want to display the Name in the ComboBox. Something like:

<DataTemplate DataType="{x:Type MyAssembly:Customer}">
    <StackPanel>
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</DataTemplate>

我可以为组合框中的选定项目选择另一个模板吗?

Can I select another Template for the selected item in a ComboBox?

解决方案

在答案的帮助下,我是这样解决的:

With help from the answers, I solved it like this:

<UserControl.Resources>
    <ControlTemplate x:Key="SimpleTemplate">
        <StackPanel>
            <TextBlock Text="{Binding Name}" />
        </StackPanel>
    </ControlTemplate>
    <ControlTemplate x:Key="ExtendedTemplate">
        <StackPanel>
            <TextBlock Text="{Binding Name}" />
            <TextBlock Text="{Binding Address}" />
        </StackPanel>
    </ControlTemplate>
    <DataTemplate x:Key="CustomerTemplate">
        <Control x:Name="theControl" Focusable="False" Template="{StaticResource ExtendedTemplate}" />
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
                <Setter TargetName="theControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>
</UserControl.Resources>

然后,我的组合框:

<ComboBox ItemsSource="{Binding Customers}" 
                SelectedItem="{Binding SelectedCustomer}"
                ItemTemplate="{StaticResource CustomerTemplate}" />

让它工作的重要部分是 Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}"(值应该是x:Null,而不是True的部分).

The important part to get it to work was Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}" (the part where value should be x:Null, not True).

推荐答案

使用上述 DataTrigger/Binding 解决方案的问题有两个方面.第一个是您实际上最终会收到一个绑定警告,提示您无法找到所选项目的相关来源.然而,更大的问题是您将数据模板弄得杂乱无章,并使它们特定于 ComboBox.

The issue with using the DataTrigger/Binding solution mentioned above are two-fold. The first is you actually end up with a binding warning that you can't find the relative source for the selected item. The bigger issue however is that you've cluttered up your data templates and made them specific to a ComboBox.

我提出的解决方案更好地遵循 WPF 设计,因为它使用 DataTemplateSelector,您可以在其上使用其 SelectedItemTemplateDropDownItemsTemplate 指定单独的模板属性以及两者的选择器"变体.

The solution I present better follows WPF designs in that it uses a DataTemplateSelector on which you can specify separate templates using its SelectedItemTemplate and DropDownItemsTemplate properties as well as ‘selector’ variants for both.

注意:针对 C#9 更新,启用可空性并在搜索过程中使用模式匹配

public class ComboBoxTemplateSelector : DataTemplateSelector {

    public DataTemplate?         SelectedItemTemplate          { get; set; }
    public DataTemplateSelector? SelectedItemTemplateSelector  { get; set; }
    public DataTemplate?         DropdownItemsTemplate         { get; set; }
    public DataTemplateSelector? DropdownItemsTemplateSelector { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container) {

        var itemToCheck = container;

        // Search up the visual tree, stopping at either a ComboBox or
        // a ComboBoxItem (or null). This will determine which template to use
        while(itemToCheck is not null
        and not ComboBox
        and not ComboBoxItem)
            itemToCheck = VisualTreeHelper.GetParent(itemToCheck);

        // If you stopped at a ComboBoxItem, you're in the dropdown
        var inDropDown = itemToCheck is ComboBoxItem;

        return inDropDown
            ? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container)
            : SelectedItemTemplate  ?? SelectedItemTemplateSelector?.SelectTemplate(item, container); 
    }
}

为了更易于在 XAML 中使用,我还包含了一个标记扩展,它只是在其 ProvideValue 函数中创建并返回上述类.

To make it easier to use in XAML, I've also included a markup extension that simply creates and returns the above class in its ProvideValue function.

public class ComboBoxTemplateSelectorExtension : MarkupExtension {

    public DataTemplate?         SelectedItemTemplate          { get; set; }
    public DataTemplateSelector? SelectedItemTemplateSelector  { get; set; }
    public DataTemplate?         DropdownItemsTemplate         { get; set; }
    public DataTemplateSelector? DropdownItemsTemplateSelector { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
        => new ComboBoxTemplateSelector(){
            SelectedItemTemplate          = SelectedItemTemplate,
            SelectedItemTemplateSelector  = SelectedItemTemplateSelector,
            DropdownItemsTemplate         = DropdownItemsTemplate,
            DropdownItemsTemplateSelector = DropdownItemsTemplateSelector
        };
}

这是你如何使用它.漂亮、干净、清晰,您的模板保持纯净"

And here's how you use it. Nice, clean and clear and your templates stay 'pure'

注意:'is:' 这是我在代码中放置类的位置的 xmlns 映射.确保导入您自己的命名空间并根据需要更改is:".

Note: 'is:' here is my xmlns mapping for where I put the class in code. Make sure to import your own namespace and change 'is:' as appropriate.

<ComboBox x:Name="MyComboBox"
    ItemsSource="{Binding Items}"
    ItemTemplateSelector="{is:ComboBoxTemplateSelector
        SelectedItemTemplate={StaticResource MySelectedItemTemplate},
        DropdownItemsTemplate={StaticResource MyDropDownItemTemplate}}" />

如果您愿意,也可以使用 DataTemplateSelectors...

You can also use DataTemplateSelectors if you prefer...

<ComboBox x:Name="MyComboBox"
    ItemsSource="{Binding Items}"
    ItemTemplateSelector="{is:ComboBoxTemplateSelector
        SelectedItemTemplateSelector={StaticResource MySelectedItemTemplateSelector},
        DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />

或者混搭!在这里,我为所选项目使用了模板,但为 DropDown 项目使用了模板选择器.

Or mix and match! Here I'm using a template for the selected item, but a template selector for the DropDown items.

<ComboBox x:Name="MyComboBox"
    ItemsSource="{Binding Items}"
    ItemTemplateSelector="{is:ComboBoxTemplateSelector
        SelectedItemTemplate={StaticResource MySelectedItemTemplate},
        DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />

此外,如果您没有为选定项或下拉项指定 Template 或 TemplateSelector,它会再次返回到基于数据类型的数据模板的常规解析,正如您所期望的那样.因此,例如,在以下情况下,所选项目已显式设置其模板,但下拉列表将继承适用于数据上下文中对象的 DataType 的任何数据模板.

Additionally, if you don't specify a Template or a TemplateSelector for the selected or dropdown items, it simply falls back to the regular resolving of data templates based on data types, again, as you would expect. So, for instance, in the below case, the selected item has its template explicitly set, but the dropdown will inherit whichever data template applies for the DataType of the object in the data context.

<ComboBox x:Name="MyComboBox"
    ItemsSource="{Binding Items}"
    ItemTemplateSelector="{is:ComboBoxTemplateSelector
        SelectedItemTemplate={StaticResource MyTemplate} />

享受吧!

这篇关于我可以为 WPF ComboBox 中的选定项目使用与下拉部分中的项目不同的模板吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆