WPF:如何在ComboBox中自定义SelectionBoxItem

9
我想在ComboBox中展示一个自定义模板/项作为已选项目(该项实际上并不存在于项目列表中,并以不同的方式进行更新)。它甚至不需要成为一个项目,只需提供一个自定义视图即可。
如何在保持当前ComboBox主题的情况下执行此操作(因此无法替换ControlTemplate)?就我所见,所有SelectionBox*属性都不可编辑,内部ComboBox使用未命名的ContentPresenter。

显然我无法展示所选项目,因为我没有单个选定的项目。 - Andrey Shchekin
请查看以下内容: https://dev59.com/DEnSa4cB1Zd3GeqPOXw7 - David Brunelle
看起来很不错(DataTrigger/{x:Null}),我今天稍后会检查一下。 - Andrey Shchekin
1
这是解决方案,谢谢。不幸的是我不能批准两个答案,而Ray Burns提供了更详细(也更具体)的解决方案,所以我会批准他的并点赞你的。 - Andrey Shchekin
唉...只差三个声望就可以重新给问题打标签了 :D - David Brunelle
显示剩余2条评论
4个回答

26

我会这样做:

<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="..."
  ... />

这段代码之所以起作用,是因为CombinedTemplate通常使用NormalItemTemplate来呈现数据,但如果没有任何ComboBoxItem的祖先,则会假设它在选择框中,因此使用SelectionBoxTemplate

请注意,这三个DataTemplates可以包含在任何层级的ResourceDictionary中(不仅仅是在Window级别),甚至可以直接放在ComboBox中,具体取决于您的喜好。


谢谢,我肯定会尝试这个。 - Andrey Shchekin
2
然而,这会生成一个绑定异常:Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ComboBoxItem', AncestorLevel='1''。我认为设置 ItemTemplateSelector 是更好的方法。这里有一个例子:http://social.msdn.microsoft.com/Forums/vstudio/en-US/0467c9ca-efb2-4506-96e7-08ce3356860a/combobox-one-template-for-selected-item-one-for-the-dropdown-list?forum=wpf - vers
1
这对我有用,但需要做一些更改。我需要将“Template = {StaticResource NormalItemTemplate}”更改为“ContentTemplate = {StaticResource NormalItemTemplate}”。我还需要将Property =“Template”更改为Property =“ContentTemplate”。 - Matt Becker
请根据Matt Becker上面的评论更新您的答案。我刚刚点赞了这个答案,因为我发现它很有帮助,主要是通过添加一些数据触发器来检查是否有任何父级ComboBoxItem来在正常模板和选择框模板之间切换的想法。Template是仅支持Control的属性,ContentPresenter不是控件,当然没有这样的属性。 - Hopeless

1
Alexey Mitev在Ray Burns的answer上的评论启发了我编写以下相当简短的实用类,现在我在所有我的WPF项目中都使用它:
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;
    }
}

有了这个工具,就可以像这样编写XAML:

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

0

如果我理解正确,您想要一个控件,它可以显示任意内容,并带有一个下拉按钮,该下拉按钮会显示一系列带有复选框的项目列表?

我甚至不会尝试重新设计 ComboBox 来实现这个功能。问题在于,ComboBox 比你所需的更专业化。如果您查看 ComboBox ControlTemplate Example,您会发现它仅使用一个 Popup 控件来显示可能值的列表。

您可以借助该模板的部分作为指导,创建一个更易于理解且提供所需功能的 UserControl。您甚至可以添加一个 SelectedItems 属性等,而这是 ComboBox 不提供的。

一个关于指导的例子: Popup 有一个 IsOpen 属性。在控件模板中,它被设置为 {TemplateBinding IsDropDownOpen},这意味着 ComboBox 类有一个 IsDropDownOpen 属性,该属性会被更改以控制 Popup 的展开/折叠。

1
自定义控件的问题在于它们不受内置样式的影响。我已经构建了一个具有SelectedItems的自定义控件,但其内部依赖于ComboBox,因为我希望默认样式可以与之配合,而无需重复定义。 - Andrey Shchekin

-1

谢谢,不幸的是,与其他答案相比,那有点太抽象了。我大致知道触发器的工作原理,但我无法想出{x:Null}的解决方案。 - Andrey Shchekin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接