如何在WPF ComboBox中显示下拉列表值/所选项的不同值?

时间:2010-10-22 10:14:28

标签: wpf combobox wpf-controls binding

我有一个WPF组合框绑定到一个带有长描述的项目列表。

绑定到ComboBox的类型具有短描述和长描述作为属性。目前,我对完整描述具有约束力。

comboBox.DisplayMemberPath = "FullDescription";

如何确保当项目被选中并在组合框中显示为单个项目时,它将显示为ShortDescription属性的值,而下拉列表将显示FullDescription

5 个答案:

答案 0 :(得分:17)

更新2011-11-14

我最近又遇到了同样的要求,我对下面发布的解决方案不太满意。这是一种更好的方法,可以在不重新模板ComboBoxItem的情况下获得相同的行为。它使用DataTemplateSelector

首先,在DataTemplate的资源中指定常规DataTemplate,下拉列表ComboBoxItemTemplateSelectorComboBox。然后将ComboBoxItemTemplateSelector引用为DynamicResource

ItemTemplateSelector
<ComboBox ...
          ItemTemplateSelector="{DynamicResource itemTemplateSelector}">
    <ComboBox.Resources>
        <DataTemplate x:Key="selectedTemplate">
            <TextBlock Text="{Binding Path=ShortDescription}"/>
        </DataTemplate>
        <DataTemplate x:Key="dropDownTemplate">
            <TextBlock Text="{Binding Path=FullDescription}"/>
        </DataTemplate>
        <local:ComboBoxItemTemplateSelector
            x:Key="itemTemplateSelector"
            SelectedTemplate="{StaticResource selectedTemplate}"
            DropDownTemplate="{StaticResource dropDownTemplate}"/>
    </ComboBox.Resources>
</ComboBox>

ComboBoxItemTemplateSelector检查容器是否是ComboBoxItem的子容器,如果是,则我们正在处理下拉项,否则它是ComboBox中的项。 / p>

public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate DropDownTemplate
    {
        get;
        set;
    }
    public DataTemplate SelectedTemplate
    {
        get;
        set;
    }
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        ComboBoxItem comboBoxItem = VisualTreeHelpers.GetVisualParent<ComboBoxItem>(container);
        if (comboBoxItem != null)
        {
            return DropDownTemplate;
        }
        return SelectedTemplate;
    }
}

GetVisualParent

public static T GetVisualParent<T>(object childObject) where T : Visual
{
    DependencyObject child = childObject as DependencyObject;
    while ((child != null) && !(child is T))
    {
        child = VisualTreeHelper.GetParent(child);
    }
    return child as T;
}

旧解决方案,需要重新模板ComboBoxItem

<SolidColorBrush x:Key="SelectedBackgroundBrush" Color="#DDD" />
<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />

<ControlTemplate x:Key="FullDescriptionTemplate" TargetType="ComboBoxItem">
    <Border Name="Border" Padding="2" SnapsToDevicePixels="true">
        <StackPanel>
            <TextBlock Text="{Binding Path=FullDescription}"/>
        </StackPanel>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsHighlighted" Value="true">
            <Setter TargetName="Border" Property="Background" Value="{StaticResource SelectedBackgroundBrush}"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="false">
            <Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

<ComboBox Name="c_comboBox" ItemsSource="{Binding}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=ShortDescription}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
    <ComboBox.ItemContainerStyle>
        <Style TargetType="{x:Type ComboBoxItem}">
            <Setter Property="Template" Value="{StaticResource FullDescriptionTemplate}" />
        </Style>
    </ComboBox.ItemContainerStyle>
</ComboBox>

这导致以下行为

alt text

答案 1 :(得分:2)

现在似乎对我不起作用,但这个确实如此:

public class ComboBoxItemTemplateSelector : DataTemplateSelector {
  public DataTemplate SelectedTemplate { get; set; }
  public DataTemplate DropDownTemplate { get; set; }

  public override DataTemplate SelectTemplate(object item, DependencyObject container) {
    var presenter = (ContentPresenter)container;
    return (presenter.TemplatedParent is ComboBox) ? SelectedTemplate : DropDownTemplate;
  }
}

答案 2 :(得分:1)

我修改了这个自定义的四舍五入WPF组合框,以显示与所选项目不同的值,并更改每个项目的颜色。

Custom ComboBox

首先,您需要创建结构:

//Structure
public class COMBOITEM
{
    string _ITEM_NAME;
    string _ITEM_SHORT_NAME;
    Brush _ITEM_COLOR;

    public string ITEM_NAME
    {
        get { return _ITEM_NAME; }
        set { _ITEM_NAME = value; }
    }

    public string ITEM_SHORT_NAME
    {
        get { return _ITEM_SHORT_NAME; }
        set { _ITEM_SHORT_NAME = value; }
    }

    public Brush ITEM_COLOR
    {
        get { return _ITEM_COLOR; }
        set { _ITEM_COLOR = value; }
    }
}

初始化结构,将其填充数据并绑定到ComboBox:

private void Load_Data()
{
    Brush Normal_Blue = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF1F4E79"));
    //Load first entry
    ObservableCollection<COMBOITEM> _Line_Data = new ObservableCollection<COMBOITEM>();
    _Line_Data.Add(new COMBOITEM() { ITEM_NAME = "Line Number 1", ITEM_SHORT_NAME = "LN 1", ITEM_COLOR = Normal_Blue });

    //Load Test Data
    for (int i = 2; i < 10; i++)
    {
        _Line_Data.Add(new COMBOITEM()
        {
            ITEM_NAME = "Line Number " + i.ToString(),
            ITEM_SHORT_NAME = "LN " + i.ToString(),
            ITEM_COLOR = (i % 2 == 0) ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red) //This just changes color
        });
    }

    //Bind data to combobox
    cb_Test.ItemsSource = _Line_Data;
}

现在将ComboBox放入设计中。要将其用作普通的ComboBox,请删除DisplayMemberPath并将“ ColorComboBoxItem”重命名为“ CustomComboBoxItem”:

<ComboBox x:Name="cb_Test" FontSize="36" Padding="1,0" MinWidth="100" MaxWidth="400" Margin="5,53,10,207" FontFamily="Calibri" Background="#FFBFBFBF" Foreground="#FF1F4E79" BorderBrush="#FF1F4E79" VerticalContentAlignment="Center" TabIndex="5" IsSynchronizedWithCurrentItem="False"
            Style="{DynamicResource RoundedComboBox}" 
            ItemContainerStyle="{DynamicResource ColorComboBoxItem}" 
            DisplayMemberPath="ITEM_SHORT_NAME" />

现在将以下样式/模板添加到App.xaml Application.Resources:

<!-- Rounded ComboBox Button -->
<Style x:Key="ComboBoxToggleButton" TargetType="ToggleButton">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ToggleButton">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition Width="32" />
                    </Grid.ColumnDefinitions>
                    <Border
                    x:Name="Border"
                    Grid.ColumnSpan="2"
                    CornerRadius="8"
                    Background="{TemplateBinding Background}"
                    BorderBrush="#FF1F4E79"
                    BorderThickness="2" 
                />

                    <Path
                    x:Name="Arrow"
                    Grid.Column="1"    
                    Fill="{TemplateBinding Foreground}"
                    Stroke="{TemplateBinding Foreground}"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Data="M 0 0 L 4 4 L 8 0 Z"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<ControlTemplate x:Key="ComboBoxTextBox" TargetType="TextBox">
    <Border x:Name="PART_ContentHost" Focusable="True" />
</ControlTemplate>

<!-- ComboBox Template -->
<Style x:Key="RoundedComboBox" TargetType="{x:Type ComboBox}">
    <Setter Property="Foreground" Value="#333" />
    <Setter Property="BorderBrush" Value="Gray" />
    <Setter Property="Background" Value="White" />
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
    <Setter Property="FontSize" Value="13" />
    <Setter Property="MinWidth" Value="150"/>
    <Setter Property="MinHeight" Value="35"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ComboBox">
                <Grid>
                    <ToggleButton
                    Cursor="Hand"
                    Name="ToggleButton"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    Background="{TemplateBinding Background}"
                    Foreground="{TemplateBinding Foreground}"
                    Style="{StaticResource ComboBoxToggleButton}"
                    Grid.Column="2"
                    Focusable="false"
                    IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
                    ClickMode="Press"/>

                    <ContentPresenter
                    Name="ContentSite"
                    IsHitTestVisible="False"
                    Content="{TemplateBinding SelectionBoxItem}"
                    ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
                    ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
                    Margin="10,3,30,3"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Left" />
                    <TextBox x:Name="PART_EditableTextBox"
                    Style="{x:Null}"
                    Template="{StaticResource ComboBoxTextBox}"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    Margin="3,3,23,3"
                    Focusable="True"                               
                    Visibility="Hidden"
                    IsReadOnly="{TemplateBinding IsReadOnly}"/>
                    <Popup
                    Name="Popup"
                    Placement="Bottom"
                    IsOpen="{TemplateBinding IsDropDownOpen}"
                    AllowsTransparency="True"
                    Focusable="False"
                    PopupAnimation="Slide">
                        <Grid
                        Name="DropDown"
                        SnapsToDevicePixels="True"               
                        MinWidth="{TemplateBinding ActualWidth}"
                        MaxHeight="{TemplateBinding MaxDropDownHeight}">
                            <Border
                            CornerRadius="10"
                            x:Name="DropDownBorder"
                            Background="#FFBFBFBF"
                            BorderThickness="2"
                            BorderBrush="#FF1F4E79"
                            />
                            <ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
                                <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
                            </ScrollViewer>
                        </Grid>
                    </Popup>

                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasItems" Value="false">
                        <Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
                    </Trigger>
                    <Trigger Property="IsGrouping" Value="true">
                        <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                    </Trigger>
                    <Trigger Property="IsEditable" Value="true">
                        <Setter Property="IsTabStop" Value="false"/>
                        <Setter TargetName="PART_EditableTextBox" Property="Visibility" Value="Visible"/>
                        <Setter TargetName="ContentSite" Property="Visibility" Value="Hidden"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
    </Style.Triggers>
</Style>

<!--This style uses the normal items.add function-->
<Style x:Key="CustomComboBoxItem" TargetType="{x:Type ComboBoxItem}">
    <Setter Property="SnapsToDevicePixels" Value="true" />
    <Setter Property="HorizontalAlignment" Value="Stretch" />
    <Setter Property="VerticalAlignment" Value="Stretch" />
    <Setter Property="FontSize" Value="30" />
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ComboBoxItem">
                <Border
                Name="Border"
                Padding="5"
                Margin="2"
                BorderThickness="2,0,0,0"
                CornerRadius="0"
                Background="Transparent"
                BorderBrush="Transparent">
                    <TextBlock TextAlignment="Left">
                    <ContentPresenter />
                    </TextBlock>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsHighlighted" Value="true">
                        <Setter TargetName="Border" Property="BorderBrush" Value="#FF3737CB"/>
                        <Setter TargetName="Border" Property="Background" Value="#FF6ACDEA"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<!--This style uses the structure to fill items and set the item color-->
<Style x:Key="ColorComboBoxItem" TargetType="{x:Type ComboBoxItem}">
    <Setter Property="SnapsToDevicePixels" Value="true" />
    <Setter Property="HorizontalAlignment" Value="Stretch" />
    <Setter Property="VerticalAlignment" Value="Stretch" />
    <Setter Property="FontSize" Value="30" />
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="Foreground" Value="{Binding ITEM_COLOR}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ComboBoxItem">
                <Border
                    Name="Border"
                    Padding="5"
                    Margin="2"
                    BorderThickness="2,0,0,0"
                    CornerRadius="0"
                    Background="Transparent"
                    BorderBrush="Transparent">
                    <TextBlock Text="{Binding ITEM_NAME}" TextAlignment="Left">
                    </TextBlock>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsHighlighted" Value="true">
                        <Setter TargetName="Border" Property="BorderBrush" Value="#FF3737CB"/>
                        <Setter TargetName="Border" Property="Background" Value="#FF6ACDEA"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

我希望这会有所帮助。

答案 3 :(得分:0)

我发现的另一个选项是在组合框文本区域上放置一个文本框。调整大小并将其对齐,使其完全覆盖,然后使用与此类似的子:

Private Sub ComboBox*_Change()
Dim T As String
T = Left(ComboBox*.Text, 1)
TextBox*.Value = T
End Sub

(用相关数字替换*) 结果是,选中后,下拉列表将照常显示列表,但位于其上的文本框将仅显示其第一个字符。

希望这有帮助。

答案 4 :(得分:0)

此解决方案适用于WPF + MVVM。

其他解决方案中的一些可行,而某些则不可行。其他一些解决方案的问题是,如果它们不起作用,则有时很难调试为什么它不起作用,特别是如果没有使用WPF的人。

我认为,最好将字符串用于绑定,然后转换为C#中的枚举,这意味着所有问题都更易于解决。

您可能需要使用ReSharper,它将自动建议任何缺少的名称空间。

创建一个具有描述属性的枚举:

public enum EnumSelectedView
{
    [Description("Drop Down 1")]
    DropDown1 = 0,

    [Description("Drop Down 2")]
    DropDown2 = 1,
}

还有一个组合框:

<ComboBox HorizontalAlignment="Right"
   VerticalAlignment="Top"
   Width="130"
   ItemsSource="{Binding AvailableSelectedViews, Mode=OneWay}"
   SelectedItem="{Binding SelectedView, Mode=TwoWay, Converter={StaticResource enumToDescriptionConverter}}"
</ComboBox>

XAML中的转换器需要指向C#类。如果您使用的是UserControl或Window,则为UserControl.ResourcesWindow.Resources

<DataTemplate.Resources>
    <converters:EnumToDescriptionConverter x:Key="enumToDescriptionConverter" />            
</DataTemplate.Resources>

在项目的任何位置添加一些扩展方法和转换器:

using System;

namespace CMCMarkets.Phantom.CoreUI.Converters
{
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Windows.Data;

    public class EnumToDescriptionConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if ((value is Enum) == false) throw new ArgumentException("Error: value is not an enum.");
            return ((Enum)value)?.GetDescriptionAttribute();
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if ((value is string) == false)
            {
                throw new ArgumentException("Error: Value is not a string");
            }
            foreach (var item in Enum.GetValues(targetType))
            {
                var asString = (item as Enum).GetDescriptionAttribute();
                if (asString == (string)value)
                {
                    return item;
                }
            }
            throw new ArgumentException("Error: Unable to match string to enum description.");
        }
    }

    public static class EnumExtensions
    {
        /// <summary>
        /// For a single enum entry, return the [Description("")] attribute.
        /// </summary>
        public static string GetDescriptionAttribute(this Enum enumObj)
        {
            FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());
            object[] attribArray = fieldInfo.GetCustomAttributes(false);
            if (attribArray.Length == 0)
            {
                return enumObj.ToString();
            }
            else
            {
                DescriptionAttribute attrib = attribArray[0] as DescriptionAttribute;
                return attrib?.Description;
            }
        }

        /// <summary>
        /// For an enum type, return a list of all possible [Description("")] attributes.
        /// </summary>
        /*
         * Example: List<string> descriptions = EnumExtensions.GetDescriptionAttributeList<MyEnumType>();
         */
        public static List<string> GetDescriptionAttributeList<T>()
        {
            return typeof(T).GetEnumValues().Cast<Enum>().Select(x => x.GetDescriptionAttribute()).ToList();
        }

        /// <summary>
        /// For an enum instance, return a list of all possible [Description("")] attributes.
        /// </summary>
        /*
         * Example:
         *
         * List<string> descriptions = typeof(CryptoExchangePricingOrGraphView).GetDescriptionAttributeList();
         */
        public static List<string> GetDescriptionAttributeList(this Type type)
        {
            return type.GetEnumValues().Cast<Enum>().Select(x => x.GetDescriptionAttribute()).ToList();
        }

        /// <summary>
        /// For an enum instance, return a list of all possible [Description("")] attributes.
        /// </summary>
        /*
         * Example:
         *
         * MyEnumType x;
         * List<string> descriptions = x.GetDescriptionAttributeList();
         */
        public static List<string> GetDescriptionAttributeList(this Enum thisEnum)
        {
            return thisEnum.GetType().GetEnumValues().Cast<Enum>().Select(x => x.GetDescriptionAttribute()).ToList();
        }
    }
}

在您的ViewModel中:

public IReadOnlyList<string> AvailableSelectedViews { get; }

在构造函数中:

 this.AvailableSelectedViews = typeof(EnumSelectedView).GetDescriptionAttributeList();

所选项目将与此绑定。它使用转换器将组合框中的字符串直接转到枚举。您也可以使用上面的扩展方法在属性更新程序中进行转换。

public EnumSelectedView SelectedView { get; set; }