ComboBox ItemTemplate 仅在下拉列表中工作

Posted

技术标签:

【中文标题】ComboBox ItemTemplate 仅在下拉列表中工作【英文标题】:ComboBox ItemTemplate only working in dropdown 【发布时间】:2021-09-24 13:40:42 【问题描述】:

我试图显示一个 ComboBox,其 ItemsSource 是控件的集合(它是 PropertyGrid 的一部分,ComboBox 应该显示控件的名称,并且用户应该能够选择其中一个控件)。这是问题的极其简化的再现:

<ComboBox ItemsSource="Binding GroupBoxes" SelectedValue="Binding SelectedGroupBox">
  <ComboBox.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="Binding Name"/>
    </DataTemplate>
  </ComboBox.ItemTemplate>          
</ComboBox>

GroupBoxes 和 SelectedGroupBox 是 ObservableCollection 和 GroupBox 类型的 DependencyProperties。

绑定起作用 - 控件名称显示在 ComboBox-DropDown 中,如果我选择不同的项目,我可以看到 SelectedGroupBox 属性已正确更新。问题:所选项目永远不会显示在 ComboBox 中。从代码中设置 SelectedGroupBox 属性也可以按预期工作 - ComboBox 引发 SelectionChanged 并且其 SelectedValue 是正确的,但它仍然不显示当前值。

如果我对任何其他类型的课程做同样的事情,一切都会按预期进行。

在寻找答案的过程中,我遇到了很多人的帖子,他们有类似的声音问题,但几乎所有帖子都是绑定问题,而这里不是这样。

编辑:

为了简化试用,下面是代码。只需将上面的 XAML 放到一个新的 Window 中,下面的代码放在后面的代码中。

public MainWindow() 
    InitializeComponent();
    this.DataContext = this;
    this.GroupBoxes = new ObservableCollection<GroupBox>();
    this.GroupBoxes.Add(new GroupBox()  Name = "AAA", Header = "AAA", Height = 100, Background = Brushes.Purple );
    this.GroupBoxes.Add(new GroupBox()  Name = "BBB", Header = "BBB", Height = 100, Background = Brushes.Purple );
    this.GroupBoxes.Add(new GroupBox()  Name = "CCC", Header = "CCC", Height = 100, Background = Brushes.Purple );
    this.GroupBoxes.Add(new GroupBox()  Name = "DDD", Header = "DDD", Height = 100, Background = Brushes.Purple );
    this.GroupBoxes.Add(new GroupBox()  Name = "EEE", Header = "EEE", Height = 100, Background = Brushes.Purple );


#region GroupBoxesProperty

public static readonly DependencyProperty GroupBoxesProperty = DependencyProperty.Register(
    "GroupBoxes", typeof(ObservableCollection<GroupBox>), typeof(MainWindow)
);

public ObservableCollection<GroupBox> GroupBoxes 
    get  return (ObservableCollection<GroupBox>)GetValue(GroupBoxesProperty); 
    set  SetValue(GroupBoxesProperty, value); 


#endregion

#region SelectedGroupBoxProperty

public static readonly DependencyProperty SelectedGroupBoxProperty = DependencyProperty.Register(
    "SelectedGroupBox", typeof(GroupBox), typeof(MainWindow),
    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (s, e) => (s as MainWindow).OnSelectedGroupBoxChanged())
);

public GroupBox SelectedGroupBox 
    get  return (GroupBox)GetValue(SelectedGroupBoxProperty); 
    set  SetValue(SelectedGroupBoxProperty, value); 


void OnSelectedGroupBoxChanged() 
    Console.WriteLine("selection is now " + this.SelectedGroupBox.Name);


#endregion

【问题讨论】:

如果您只使用 Binding 而不是 Binding Name 它会起作用并显示控件的默认 ToString 方法结果吗? 不,它不会改变任何东西;它显示 ToString()-content 而不是 DropDown 中的 Name 但仍然存在不可见 SelectedValue 的问题。 你的WPF Trace Settings都打开了吗?这些默认情况下是关闭的。 Visual Studio 吃掉了很多 WPF 警告和错误。 我把它们都打开了,但没有问题。 【参考方案1】:

由于某些非常复杂的原因,ComboBox 公开了一个名为 SelectionBoxItem 的只读属性。 ComboBox 模板中的内容展示器绑定在此属性上。正是 SelectionBoxItem 公开了非 UI 元素的字符串表示,允许您查看所选值。使用此属性可以防止内容呈现者使用数据模板。这就是模板适用于下拉列表但不适用于所选项目的原因。这是导致问题的默认 ComboBox 模板的一部分:

<ContentPresenter IsHitTestVisible="false"
    Margin="8,1,1,1"
    Content="TemplateBinding SelectionBoxItem"
    ContentTemplate="TemplateBinding SelectionBoxItemTemplate"
    ContentTemplateSelector="TemplateBinding ItemTemplateSelector"
    VerticalAlignment="TemplateBinding VerticalContentAlignment"
    HorizontalAlignment="TemplateBinding HorizontalContentAlignment"
    SnapsToDevicePixels="TemplateBinding SnapsToDevicePixels"/>

但是,您可以创建自己的 ComboBox 样式,该样式覆盖默认 ContentPresenter 并使用 SelectedItem 代替 SelectionBoxItem 和 ItemTemplate 代替 SelectionItemBoxTemplate。这将解决问题。

【讨论】:

很好的答案,很好的解释,你的修复工作完美,谢谢!【参考方案2】:

使用DisplayMemberPath 而不是绑定到名称:

 <Combobox DisplayMemberPath="Name" ... />

您看到的行为的原因可能是您需要为所选项目的模板设置另一个属性:http://msdn.microsoft.com/en-us/library/system.windows.controls.combobox.selectionboxitemtemplate.aspx


更新:我在没有检查您的代码的情况下编写了答案,对此深表歉意。现在我已经阅读了您的代码并注意到您正在绑定 SelectedValue 属性。我不认为这是在您的情况下绑定的最佳属性,通常应该使用属性SelectedItem。我记得我从来不需要对其他答案中提到的SelectionBoxItem 做任何事情,这可能是因为SelectedValueSelectedItem 属性的行为不同,我倾向于尽可能使用SelectedItem。在你的情况下,我也会使用它。

【讨论】:

他们应该使用DisplayMemberPath 而不是ItemTemplate,但这并不能解决问题。 在上面的示例中使用 DisplayMemberPath 较短,但在我的实际应用程序中,我需要访问对象的其他属性以及更改它们显示方式的可能性。正如 icirellik 所说,它并不能解决问题。 @wilford,我已经更新了答案。你能用SelectedItem而不是SelectedValue来测试你的原始代码吗? 我试过了,但只是用 SelectedItem 替换 SelectedValue 并不会改变 SelectionBoxItemTemplate 用于选择而不是 ItemTemplate (我期望并忽略检查)的事实,所以行为是相同的.而且我只有对 SelectionBoxItemTemplate 的读取权限,所以这就是问题所在。从我读到的关于 SelectedValue 和 SelectedItem 之间的区别来看,我认为我使用哪一个没有区别。 @wilford,好的,现在我看到它发生了,因为您的组合框中有 GroupBoxes。之前没提过。这取决于你,但我会尽量避免这种情况。【参考方案3】:

这个link 提供了一个解决方案和一个演示。诀窍是在需要控制 SelectedItem 的外观的情况下使用 DataTemplateSelector 而不是 DataTemplate,例如:

<ComboBox ItemsSource="Binding GroupBoxes" SelectedValue="Binding SelectedGroupBox">
    <ComboBox.ItemTemplateSelector>
            <ts:ComboBoxItemTemplateSelector>
                <ts:ComboBoxItemTemplateSelector.SelectedTemplate>
                    <DataTemplate>
                    <TextBlock Text="Binding Name"/>
                    </DataTemplate>
                </ts:ComboBoxItemTemplateSelector.SelectedTemplate>                  
            </ts:ComboBoxItemTemplateSelector>
        </ComboBox.ItemTemplateSelector>     
</ComboBox>

public class ComboBoxItemTemplateSelector : DataTemplateSelector

    public DataTemplate SelectedTemplate  get; set; 

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    
        return SelectedTemplate;     
    

【讨论】:

注意,一旦你设置了IsEditable="True",这个解决方案就不再起作用了

以上是关于ComboBox ItemTemplate 仅在下拉列表中工作的主要内容,如果未能解决你的问题,请参考以下文章

C#/WPF高手进!ComboBox.ItemTemplate用TextBlock正常,用TextBox就报错!

C#/WPF高手进!ComboBox.ItemTemplate用TextBlock正常,用TextBox就报错!

如何获取 ComboBox 的值?

在 ListView 内显示绑定 ComboBox

读取 SQL 表单元格值,仅更改 ComboBox.text 而不更改 ComboBox 集合项

C/S combobox 数据绑定?