Tengo una WPF Combobox que está lleno de, digamos, el Cliente objetos. Tengo un DataTemplate:

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

De esta manera, cuando abro mi ComboBox, puedo ver a los diferentes Clientes con su Nombre y, a continuación, la Dirección.

Pero cuando voy a seleccionar un Cliente, solo quiero mostrar el Nombre en el ComboBox. Algo así como:

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

Puedo seleccionar otra Plantilla para el elemento seleccionado en un control ComboBox?

Solución

Con la ayuda de las respuestas, he resuelto así:

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

Entonces, mi ComboBox:

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

La parte importante para conseguir que el trabajo se Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}" (la parte en donde el valor debe ser x:Null, no es Cierto).

  • Su solución funciona, pero me da errores en la ventana de Salida. System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ComboBoxItem', AncestorLevel='1''. BindingExpression:Path=IsSelected; DataItem=null; target element is 'ContentPresenter' (Name=''); target property is 'NoTarget' (type 'Object')
  • Yo recuerdo haber visto estos errores también. Pero yo ya no estoy en el proyecto (o incluso en la empresa), así que no puedo comprobar esto, lo siento.
  • La mención de la Ruta de Enlace en el DataTrigger es innecesario. Como el ComboBoxItem se selecciona una plantilla diferente será aplicado para el control y la DataTrigger de unión ya no será capaz de encontrar un antepasado de tipo ComboBoxItem en su árbol de elementos. Por lo tanto, la comparación con el valor siempre será un éxito. Este enfoque funciona porque el árbol Visual de la ComboBoxItem es diferente dependiendo de si está activada o mostrará en el menú emergente.
InformationsquelleAutor Peter | 2011-01-12

6 Comentarios

  1. 57

    El problema con el uso de la DataTrigger/solución Vinculante mencionado anteriormente son de dos tipos. La primera es que en realidad terminan con un enlace de advertencia de que usted no puede encontrar la relación de origen para el elemento seleccionado. El mayor problema sin embargo es que has desordenado de seguridad de sus datos de plantillas y les hizo específico de un ComboBox.

    La solución que se me presentan de la siguiente manera WPF diseños en los que se utiliza un DataTemplateSelector en la que puede especificar plantillas separadas mediante su SelectedItemTemplate y DropDownItemsTemplate propiedades así como el ‘selector’ variantes para ambos.

    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); 
        }
    }

    Nota: Para simplificar, mi código de ejemplo siguiente se usa la nueva ‘?.’ característica de C#6 (VS 2015). Si estás utilizando una versión anterior, simplemente quitar el ‘?’ y comprobar explícitamente null antes de la llamada ‘SelectTemplate’ anterior y devolver null de lo contrario, así:

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

    También he incluido una extensión de marcado que simplemente crea y devuelve la clase anterior para la comodidad en el 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
            };
        }
    }

    Y he aquí cómo usted lo utiliza. Agradable, limpio y claro y sus plantillas estancia ‘puro’

    Nota: ‘es:’ aquí está mi xmlns asignación de donde puedo poner la clase en el código. Asegúrese de importar su propio espacio de nombres y el cambio » es:», según corresponda.

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

    También puede utilizar DataTemplateSelectors si usted prefiere…

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

    O mezclar y combinar! En este caso estoy usando una plantilla para el elemento seleccionado, pero un selector de plantilla para los elementos de lista Desplegable.

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

    Además, si no se especifica una Plantilla o un TemplateSelector para el seleccionado o elementos de lista desplegable, simplemente se cae de nuevo a la regular de la resolución de las plantillas de datos basado en tipos de datos, de nuevo, como era de esperar. Así, por ejemplo, en el siguiente caso, el elemento seleccionado tiene su plantilla de establecer explícitamente, pero la lista desplegable heredarán lo que los datos de la plantilla se aplica para el Tipo de datos del objeto en el contexto de datos.

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

    Disfrutar!

    • Muy fresco. Y me han hecho los de unión advertencias (nunca supe de dónde venían, pero no realmente tomar una mirada tampoco). Realmente puedo comprobarlo ahora mismo, pero me podría en el futuro.
    • Me alegra ser de ayuda. Acabo de saber si usted está usando esto en el código, la instrucción return (return inDropDown arriba) utiliza el nuevo C#6 ?. la sintaxis de modo que si no estás usando VS 2015, acaba de quitar el ‘?’ y comprobar explícitamente para valores nulos antes de llamar a SelectTemplate. Voy a añadir que para el código.
    • Me quito el sombrero ante usted una solución reutilizable!
    • Gracias! Se los agradezco. Si puedes, por favor vote por ella!
    • Por alguna razón, cuando puedo implementar esta solución de la ComboBoxTemplateSelector código no se ejecuta nunca, no hay ningún enlace de errores tampoco.
    • Suena como su XAML no está configurado correctamente. Trate de usar su propio TemplateSelector. Si eso no funciona, asegúrese de que el XAML usted piensa que está siendo utilizado en realidad está siendo utilizado por cambiar otras propiedades, como el color, la fuente, etc.

  2. 31

    Solución Simple:

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

    (Tenga en cuenta que el elemento que se selecciona y se muestra en el cuadro y no la lista no está dentro de un ComboBoxItem de ahí el gatillo en Null)

    Si usted desea cambiar la totalidad de la plantilla que usted puede hacer esto mediante el gatillo, por ejemplo,aplicar diferentes ContentTemplate para ContentControl. Esto también permite mantener un defecto DataTypebasado en la selección de la plantilla, si usted acaba de cambiar la plantilla para este selectivo caso, por ejemplo:

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

    Tenga en cuenta que este método causa de unión de los errores, como la relativa fuente no se encuentra para el elemento seleccionado. Para un método alternativo ver MarqueIV la respuesta.

    • Yo quería usar dos Plantillas, para mantenerlos separados. He utilizado el código de un proyecto de ejemplo de este sitio: developingfor.net/net/dynamically-switch-wpf-datatemplate.html. Pero mientras trabajaba para un ListBox, no trabajo para un ComboBox. Su última frase resuelto o a mí, sin embargo. El elemento seleccionado en un control ComboBox no tiene IsSelected = True, pero es Null. Véase mi edición anterior de código completo de cómo lo resolví. Muchas gracias!
    • Me alegro de que era útil aunque no era exactamente lo que usted pidió. Yo no sabía acerca de la nula cosa antes de intentar responder a tu pregunta, bien, he experimentado y descubierto de esa manera.
    • IsSelected no acepta valores null y por lo que nunca puede ser NULL. Usted no necesita Path=IsSelected, debido a que la verificación NULL para un entorno ComboBoxItem es totalmente suficiente.
    • A veces el corto de texto no se muestra para mí, aunque el ShortName se establece la propiedad y OnPropertyChanged etc. Se supone que debemos conseguir un error de enlace? Este aparece cuando el campo nombre corto va de vacío (no se muestra correctamente) se llena, así como en el inicio del Sistema».Windows.Error de datos: 4 : No se puede encontrar la fuente para el enlace con la referencia ‘RelativeSource FindAncestor, AncestorType=’Sistema.Windows.Controles.ComboBoxItem’, AncestorLevel=’1″. BindingExpression:(sin ruta); DataItem=null; elemento de destino es «ContentControl’ (Nombre=»); propiedad de destino es «NoTarget’ (tipo ‘Object’)»
    • No tengo idea de lo que sus circunstancias concretas son así que no puedo darte ningún consejo. No he tenido ningún problema con esto, los enlaces están absolutamente estándar. No es usted el uso de Artiom‘s enfoque? (Como usted menciona ShortName.)
    • Estoy haciendo su camino a + a través de springy76 la sugerencia. Si usted no está teniendo este mismo error de enlace, a continuación, que podría ser un buen lugar para mí para empezar a buscar.
    • ver a mi adición. (Sí, sé que esto es viejo, pero todavía se puede como mi respuesta para el futuro.)
    • Yo realmente prefiero hacerlo de esta manera porque es en general mucho menos código. Y fáciles de ver en el código xaml de la plantilla. Gracias por compartir esta HB!

  3. 1

    Yo iba a sugerir el uso de la combinación de un ItemTemplate para el combo de artículos, con el parámetro de Texto como el título de la selección, pero veo que ComboBox no respeta el parámetro de Texto.

    He tratado con algo similar al reemplazar el ComboBox ControlTemplate. Aquí está el MSDN sitio web con un ejemplo .NET 4.0.

    En mi solución, me cambio la ContentPresenter en la lista desplegable plantilla de tener que enlazar al Texto, con su ContentTemplate enlazado a una simple plantilla de datos que contiene un TextBlock así:

    <DataTemplate x:Uid="DataTemplate_1" x:Key="ComboSelectionBoxTemplate">
        <TextBlock x:Uid="TextBlock_1" Text="{Binding}" />
    </DataTemplate>

    con esto en la ControlTemplate:

    <ContentPresenter Name="ContentSite" IsHitTestVisible="False" Content="{TemplateBinding Text}" ContentTemplate="{StaticResource ComboSelectionBoxTemplate}" Margin="3,3,23,3" VerticalAlignment="Center" HorizontalAlignment="Left"/>

    Con este enlace enlace, yo soy capaz de controlar el Combo de selección de pantalla directamente a través del parámetro de Texto en el control (el cual me unen a un valor apropiado en mi ViewModel).

    • No muy seguro de que esto es lo que estoy buscando. Quiero el look de un ComboBox que no se «activa» (es decir, que el usuario no haya hecho clic en él, no es ‘abrir’), para que muestre sólo una pieza de texto. Pero luego, cuando el usuario hace clic en él, se debe abrir/desplegable, y cada elemento debe presentar dos piezas de texto (por lo tanto, una plantilla diferente).
    • Si usted experimentar con el código de arriba, creo que voy a llegar a donde usted quiere ir. Por medio de esta plantilla de control, puede controlar el colapsado texto de la combinación a través de su propiedad de Texto (o lo que sea de propiedad gusta) lo que le permite mostrar su simple texto no seleccionado. Usted puede modificar el elemento individual de los textos mediante la especificación de la plantilla ItemTemplate al crear el combobox. (El ItemTemplate de suponer que tenga un stackpanel y dos bloques de texto, o cualquier formato que usted desee.)
  4. 1

    He utilizado enfoque de la próxima

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

    Y el comportamiento

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

    trabajó como un encanto. No le gusta bastante Cargado de eventos aquí, pero usted puede arreglar si desea

  5. 0

    Sí. Utiliza un Selector de Plantilla para determinar qué plantilla para enlazar en tiempo de ejecución. Por lo tanto, si IsSelected = False, entonces el Uso de esta plantilla, si IsSelected = True, el uso de esta otra plantilla.

    De La Nota:
    Una vez que se desea implementar un selector de plantilla, tendrá que dar las plantillas de nombres de clave.

  6. 0

    Además de lo dicho por H. B. respuesta, la Unión de Error se puede evitar con un Convertidor. El siguiente ejemplo está basado a partir de la Solución editado por la OP sí mismo.

    La idea es muy simple: obligar a algo que siempre existe (Control) y hacer la verificación correspondiente en el interior del convertidor.
    La parte pertinente de la modificación de XAML es el siguiente. Por favor, tenga en cuenta que Path=IsSelected nunca fue realmente necesario y ComboBoxItem se sustituye con Control para evitar la unión de los errores.

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

    El C# Converter código es el siguiente:

    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();
    }
    }

Dejar respuesta

Please enter your comment!
Please enter your name here