我可以为 WPF ComboBox 中的选定项目使用与下拉部分中的项目不同的模板吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4672867/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Can I use a different Template for the selected item in a WPF ComboBox than for the items in the dropdown part?
提问by Peter
I have a WPF Combobox which is filled with, say, Customer objects. I have a DataTemplate:
我有一个 WPF Combobox,里面装满了 Customer 对象。我有一个数据模板:
<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.
这样,当我打开 ComboBox 时,我可以看到不同的客户及其名称,在其下方是地址。
But when I select a Customer, I only want to display the Name in the ComboBox. Something like:
但是当我选择一个客户时,我只想在 ComboBox 中显示名称。就像是:
<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?
我可以为 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).
让它工作的重要部分是Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}"
(值应该是 x:Null,而不是 True 的部分)。
采纳答案by Mark A. Donohoe
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.
使用上述 DataTrigger/Binding 解决方案的问题有两个方面。第一个是您实际上最终会收到一个绑定警告,提示您无法找到所选项目的相关来源。然而,更大的问题是您将数据模板弄得杂乱无章,并使它们特定于 ComboBox。
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.
我提出的解决方案更好地遵循 WPF 设计,因为它使用 a DataTemplateSelector
,您可以使用它的SelectedItemTemplate
和DropDownItemsTemplate
属性以及两者的“选择器”变体指定单独的模板。
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 != null && !(itemToCheck is ComboBoxItem) && !(itemToCheck is ComboBox))
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);
}
}
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:
注意:为简单起见,我这里的示例代码使用了新的“?.” C#6 (VS 2015) 的特性。如果您使用的是旧版本,只需删除“?” 并在调用上面的“SelectTemplate”之前显式检查空值,否则返回空值,如下所示:
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.
为了在 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.
注意:'is:' 这里是我在代码中放置类的位置的 xmlns 映射。确保导入您自己的命名空间并根据需要更改“is:”。
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MySelectedItemTemplate},
DropdownItemsTemplate={StaticResource MyDropDownItemTemplate}}" />
You can also use DataTemplateSelectors if you prefer...
如果您愿意,也可以使用 DataTemplateSelectors...
<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.
或者混搭!在这里,我为所选项目使用了模板,但为 DropDown 项目使用了模板选择器。
<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.
此外,如果您没有为选定项或下拉项指定 Template 或 TemplateSelector,它会再次返回到基于数据类型的数据模板的常规解析,正如您所期望的那样。因此,例如,在以下情况下,所选项目已显式设置其模板,但下拉列表将继承适用于数据上下文中对象的 DataType 的任何数据模板。
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MyTemplate} />
Enjoy!
享受!
回答by H.B.
Simple solution:
简单的解决方案:
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Address}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
(Note that the element that is selected and displayed in the box and not the list is not inside a ComboBoxItem
hence the trigger on Null
)
(请注意,在框中而不是列表中选择并显示的元素不在 a 内,ComboBoxItem
因此触发 on Null
)
If you want to switch out the whole template you can do that as well by using the trigger to e.g. apply a different ContentTemplate
to a ContentControl
. This also allows you to retain a default DataType
-based template selection if you just change the template for this selective case, e.g.:
如果您想切换整个模板,您也可以通过使用触发器来实现,例如将不同的应用ContentTemplate
到ContentControl
. DataType
如果您只是更改此选择性案例的模板,这也允许您保留基于默认的模板选择,例如:
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}"
Value="{x:Null}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- ... -->
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ComboBox.ItemTemplate>
Note that this method will cause binding errors as the relative source is not found for the selected item. For an alternate approach see MarqueIV's answer.
请注意,此方法将导致绑定错误,因为找不到所选项目的相关来源。有关替代方法,请参阅MarqueIV 的回答。
回答by cunningdave
I was going to suggest using the combination of an ItemTemplate for the combo items, with the Text parameter as the title selection, but I see that ComboBox doesn't respect the Text parameter.
我打算建议对组合项使用 ItemTemplate 的组合,使用 Text 参数作为标题选择,但我看到 ComboBox 不尊重 Text 参数。
I dealt with something similar by overriding the ComboBox ControlTemplate. Here's the MSDN websitewith a sample for .NET 4.0.
我通过覆盖 ComboBox ControlTemplate 处理了类似的事情。这是带有 .NET 4.0 示例的 MSDN网站。
In my solution, I change the ContentPresenter in the ComboBox template to have it bind to Text, with its ContentTemplate bound to a simple DataTemplate that contains a TextBlock like so:
在我的解决方案中,我更改了 ComboBox 模板中的 ContentPresenter 以使其绑定到 Text,其 ContentTemplate 绑定到一个包含 TextBlock 的简单 DataTemplate,如下所示:
<DataTemplate x:Uid="DataTemplate_1" x:Key="ComboSelectionBoxTemplate">
<TextBlock x:Uid="TextBlock_1" Text="{Binding}" />
</DataTemplate>
with this in the ControlTemplate:
在 ControlTemplate 中使用这个:
<ContentPresenter Name="ContentSite" IsHitTestVisible="False" Content="{TemplateBinding Text}" ContentTemplate="{StaticResource ComboSelectionBoxTemplate}" Margin="3,3,23,3" VerticalAlignment="Center" HorizontalAlignment="Left"/>
With this binding link, I am able to control the Combo selection display directly via the Text parameter on the control (which I bind to an appropriate value on my ViewModel).
使用此绑定链接,我可以通过控件上的 Text 参数(我绑定到我的 ViewModel 上的适当值)直接控制组合选择显示。
回答by Artiom
I used next approach
我使用了下一个方法
<UserControl.Resources>
<DataTemplate x:Key="SelectedItemTemplate" DataType="{x:Type statusBar:OffsetItem}">
<TextBlock Text="{Binding Path=ShortName}" />
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<ComboBox DisplayMemberPath="FullName"
ItemsSource="{Binding Path=Offsets}"
behaviors:SelectedItemTemplateBehavior.SelectedItemDataTemplate="{StaticResource SelectedItemTemplate}"
SelectedItem="{Binding Path=Selected}" />
<TextBlock Text="User Time" />
<TextBlock Text="" />
</StackPanel>
And the behavior
和行为
public static class SelectedItemTemplateBehavior
{
public static readonly DependencyProperty SelectedItemDataTemplateProperty =
DependencyProperty.RegisterAttached("SelectedItemDataTemplate", typeof(DataTemplate), typeof(SelectedItemTemplateBehavior), new PropertyMetadata(default(DataTemplate), PropertyChangedCallback));
public static void SetSelectedItemDataTemplate(this UIElement element, DataTemplate value)
{
element.SetValue(SelectedItemDataTemplateProperty, value);
}
public static DataTemplate GetSelectedItemDataTemplate(this ComboBox element)
{
return (DataTemplate)element.GetValue(SelectedItemDataTemplateProperty);
}
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uiElement = d as ComboBox;
if (e.Property == SelectedItemDataTemplateProperty && uiElement != null)
{
uiElement.Loaded -= UiElementLoaded;
UpdateSelectionTemplate(uiElement);
uiElement.Loaded += UiElementLoaded;
}
}
static void UiElementLoaded(object sender, RoutedEventArgs e)
{
UpdateSelectionTemplate((ComboBox)sender);
}
private static void UpdateSelectionTemplate(ComboBox uiElement)
{
var contentPresenter = GetChildOfType<ContentPresenter>(uiElement);
if (contentPresenter == null)
return;
var template = uiElement.GetSelectedItemDataTemplate();
contentPresenter.ContentTemplate = template;
}
public static T GetChildOfType<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
worked like a charm. Don't like pretty much Loaded event here but you can fix it if you want
像魅力一样工作。不喜欢这里的 Loaded 事件,但你可以根据需要修复它
回答by raf
In addition to what said by H.B. answer, the Binding Error can be avoided with a Converter. The following example is based from the Solution edited by the OP himself.
除了HB answer所说的之外,还可以使用转换器避免绑定错误。以下示例基于OP 本人编辑的解决方案。
The idea is very simple: bind to something that alway exists (Control
) and do the relevant check inside the converter.
The relevant part of the modified XAML is the following. Please note that Path=IsSelected
was never really needed and ComboBoxItem
is replaced with Control
to avoid the binding errors.
这个想法很简单:绑定到一直存在的东西 ( Control
) 并在转换器内部进行相关检查。修改后的 XAML 的相关部分如下。请注意,Path=IsSelected
从未真正需要它并ComboBoxItem
替换为Control
以避免绑定错误。
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Control}},
Converter={StaticResource ComboBoxItemIsSelectedConverter}}"
Value="{x:Null}">
<Setter TargetName="theControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
</DataTrigger>
The C# Converter code is the following:
C#转换器代码如下:
public class ComboBoxItemIsSelectedConverter : IValueConverter
{
private static object _notNull = new object();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// value is ComboBox when the item is the one in the closed combo
if (value is ComboBox) return null;
// all the other items inside the dropdown will go here
return _notNull;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
回答by CodeWarrior
Yes. You use a Template Selectorto determine which template to bind at run-time. Thus if IsSelected = False then Use this template, if IsSelected = True, use this other template.
是的。您使用模板选择器来确定在运行时绑定哪个模板。因此,如果 IsSelected = False 则使用此模板,如果 IsSelected = True,则使用此其他模板。
Of Note: Once you implement your template selector, you will need to give the templates keynames.
注意:一旦你实现了你的模板选择器,你将需要给模板键名。