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

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

问题描述

我有一个WPF组合框,填充了,比如,客户对象。我有一个DataTemplate:

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

这样,当我打开我的ComboBox,我可以看到不同的客户,地址。



但是当我选择一个客户时,我只想在ComboBox中显示名称。例如:

 < DataTemplate DataType ={x:Type MyAssembly:Customer}> 
< StackPanel>
< TextBlock Text ={Binding Name}/>
< / StackPanel>
< / DataTemplate>我可以为ComboBox中的所选项目选择另一个模板吗?



解决方案



在答案的帮助下,我解决了这个问题:

 < 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 =theControlFocusable =FalseTemplate ={StaticResource ExtendedTemplate}/>
< DataTemplate.Triggers>
< DataTrigger Binding ={Binding RelativeSource = {RelativeSource Mode = FindAncestor,AncestorType = {x:Type ComboBoxItem}},Path = IsSelected}Value ={x:Null}>
< Setter TargetName =theControlProperty =TemplateValue ={StaticResource SimpleTemplate}/>
< / DataTrigger>
< /DataTemplate.Triggers>
< / DataTemplate>
< /UserControl.Resources>

然后,我的ComboBox:

 < 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,not True。)

解决方案

使用上述DataTrigger / Binding解决方案的问题有两个方面。第一个是你实际上结束了一个绑定警告,你找不到所选项的相关源。更大的问题是,你混乱了你的数据模板,并使它们特定于一个ComboBox。



我提出的解决方案更好地遵循WPF设计,

  public class ComboBoxTemplateSelector:DataTemplateSelector $ DataTemplateSelector:DataTemplateSelector:DataTemplateSelector b $ b {
public DataTemplate SelectedItemTemplate {get;组; }
public DataTemplateSelector SelectedItemTemplateSelector {get;组; }
public DataTemplate DropdownItemsTemplate {get;组; }
public DataTemplateSelector DropdownItemsTemplateSelector {get;组; }

public override DataTemplate SelectTemplate(object item,DependencyObject container)
{
var parent = container;

while(parent!= null&&!(parent是ComboBoxItem)&&!(parent是ComboBox))
parent = VisualTreeHelper.GetParent(parent);

var inDropDown =(parent is ComboBoxItem);

return inDropDown
?下拉项目模板DropdownItemsTemplateSelector?.SelectTemplate(item,container)
:SelectedItemTemplate? SelectedItemTemplateSelector?.SelectTemplate(item,container);
}
}




,我的示例代码在这里使用C#6(VS 2015)的新'?'功能。如果你使用的是旧版本,只需删除'?'并在上面调用SelectTemplate之前显式检查null,否则返回null,如下所示:




  return inDropDown 
?下拉项目模板
((DropdownItemsTemplateSelector!= null)
?DropdownItemsTemplateSelector.SelectTemplate(item,container)
:null)
:SelectedItemTemplate?
((SelectedItemTemplateSelector!= null)
?SelectedItemTemplateSelector.SelectTemplate(item,container)
:null)

我还包括一个标记扩展,只是为了方便在XAML中创建和返回上述类。

  public class ComboBoxTemplateSelectorExtension:MarkupExtension 
{
public DataTemplate SelectedItemTemplate {get;组; }
public DataTemplateSelector SelectedItemTemplateSelector {get;组; }
public DataTemplate DropdownItemsTemplate {get;组; }
public DataTemplateSelector DropdownItemsTemplateSelector {get;组; }

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

这里是如何使用它。


注意:'is:'这里是我的xmlns映射,类中的代码。请确保导入自己的命名空间,并酌情更改is:。




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

如果您喜欢...也可以使用DataTemplateSelectors ...

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

或混合搭配!这里我使用一个模板作为选择的项目,但是一个模板选择器为DropDown项目。

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

此外,如果您未为所选或下拉项目指定Template或TemplateSelector只是回到基于数据类型的数据模板的常规解决,再次,正如你所期望的。因此,例如,在下面的情况下,所选项目的模板被明确设置,但下拉菜单将继承数据上下文中对象的DataType的任何数据模板。

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

享受!


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>

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

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?

Solution

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>

Then, my ComboBox:

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

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).

解决方案

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.

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

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 parent = container;

        while(parent != null && !(parent is ComboBoxItem) && !(parent is ComboBox))
            parent = VisualTreeHelper.GetParent(parent);

        var inDropDown = (parent is ComboBoxItem);

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

Note: For simplicity, my example code here uses the new '?.' feature of C#6 (VS 2015). If you're using an older version, simply remove the '?' and explicitly check for null before calling 'SelectTemplate' above and return null otherwise like so:

return inDropDown
    ? DropdownItemsTemplate ??
        ((DropdownItemsTemplateSelector != null)
            ? DropdownItemsTemplateSelector.SelectTemplate(item, container)
            : null)
    : SelectedItemTemplate ??
        ((SelectedItemTemplateSelector != null)
            ? SelectedItemTemplateSelector.SelectTemplate(item, container)
            : null)

I've also included a markup extension that simply creates and returns the above class for convenience in XAML.

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)
    {
        return 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'

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}}" />

You can also use DataTemplateSelectors if you prefer...

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

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}}" />

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} />

Enjoy!

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

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