style change and wpf templates combobox

change - ¿Puedo usar una Plantilla diferente para el elemento seleccionado en un ComboBox de WPF que para los artículos en la parte desplegable?



wpf combobox change (6)

Además de lo dicho por la respuesta de HB , el error de encuadernación puede evitarse con un convertidor. El siguiente ejemplo se basa en la Solución editada por el propio OP .

La idea es muy simple: vincularse a algo que siempre existe ( Control ) y hacer la comprobación correspondiente dentro del convertidor. La parte relevante del XAML modificado es la siguiente. Tenga en cuenta que Path=IsSelected nunca fue realmente necesario y ComboBoxItem se reemplaza con Control para evitar los errores de enlace.

<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ódigo del Convertidor C # 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(); } }

Tengo un Combobox de WPF que está lleno, por ejemplo, de objetos del Cliente. 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 los diferentes Clientes con su Nombre y, debajo de eso, la Dirección.

Pero cuando selecciono un cliente, solo quiero mostrar el nombre en el ComboBox. Algo como:

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

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

Solución

Con la ayuda de las respuestas, lo resolví 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 más importante para que funcione es Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}" (la parte donde el valor debe ser x: nulo, no verdadero).


El problema con el uso de la solución DataTrigger / Binding mencionada anteriormente es doble. La primera es que realmente terminas con una advertencia vinculante de que no puedes encontrar la fuente relativa para el elemento seleccionado. El problema más grande, sin embargo, es que ha abarrotado sus plantillas de datos y las ha hecho específicas para un ComboBox.

La solución que presento sigue mejor los diseños WPF en que usa un DataTemplateSelector en el que puede especificar plantillas separadas utilizando las propiedades SelectedItemTemplate y DropDownItemsTemplate, así como los selectores 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 parent = container; // Search up the visual tree, stopping at either a ComboBox or // a ComboBoxItem (or null). This will determine which template to use while(parent != null && !(parent is ComboBoxItem) && !(parent is ComboBox)) parent = VisualTreeHelper.GetParent(parent); // If you stopped at a ComboBoxItem, you''re in the dropdown var inDropDown = (parent is ComboBoxItem); return inDropDown ? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container) : SelectedItemTemplate ?? SelectedItemTemplateSelector?.SelectTemplate(item, container); } }

Nota: para simplificar, mi código de ejemplo aquí usa el nuevo ''?.'' característica de C # 6 (VS 2015). Si está utilizando una versión anterior, simplemente elimine el ''?'' y compruebe explícitamente null antes de llamar a ''SelectTemplate'' arriba y devuelva null de otra manera, así:

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

También incluí una extensión de marcado que simplemente crea y devuelve la clase anterior para mayor comodidad en 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 así es como lo usas. Agradable, limpio y claro, y tus plantillas se mantienen "puras"

Nota: ''es:'' aquí está mi asignación xmlns para donde puse la clase en el código. Asegúrese de importar su propio espacio de nombres y cambie ''es:'' según corresponda.

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

También puede usar DataTemplateSelectors si lo prefiere ...

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

¡O mezclar y combinar! Aquí estoy usando una plantilla para el elemento seleccionado, pero un selector de plantilla para los artículos DropDown.

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

Además, si no especifica una Plantilla o un TemplateSelector para los elementos seleccionados o desplegables, simplemente vuelve a la resolución regular de las plantillas de datos basadas en tipos de datos, una vez más, como era de esperar. Por lo tanto, por ejemplo, en el caso siguiente, el elemento seleccionado tiene su plantilla establecida explícitamente, pero el menú desplegable heredará la plantilla de datos que se aplique al DataType del objeto en el contexto de datos.

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

¡Disfrutar!


Iba a sugerir el uso de la combinación de una ItemTemplate para los elementos combinados, con el parámetro Text como la selección del título, pero veo que ComboBox no respeta el parámetro Text.

He tratado algo similar anulando la ComboBox ControlTemplate. Aquí está el website MSDN con una muestra para .NET 4.0.

En mi solución, cambio el ContentPresenter en la plantilla de ComboBox para que se vincule a Texto, con su ContentTemplate vinculado a una simple DataTemplate que contiene un TextBlock así:

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

con esto en ControlTemplate:

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

Con este enlace de enlace, puedo controlar la pantalla de selección de Combo directamente a través del parámetro de Texto en el control (que ato a un valor apropiado en mi ViewModel).


Sí. Utiliza un Selector de plantillas para determinar qué plantilla vincular en el tiempo de ejecución. Por lo tanto, si IsSelected = False, use esta plantilla, si IsSelected = True, use esta otra plantilla.

Nota: una vez que implemente su selector de plantilla, tendrá que darles los nombres de las plantillas.


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 en la lista no está dentro de un ComboBoxItem por lo tanto, el desencadenador en Null )

Si desea cambiar la plantilla completa, puede hacerlo también utilizando el activador para, por ejemplo, aplicar una ContentTemplate diferente a ContentControl . Esto también le permite retener una selección de plantilla basada en DataType defecto si solo cambia la plantilla para este caso selectivo, 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 causará errores de enlace ya que la fuente relativa no se encuentra para el elemento seleccionado. Para un enfoque alternativo, vea la respuesta de MarqueIV .


Usé el próximo acercamiento

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

trabajado como un encanto. No me gusta el evento Loaded aquí pero puedes arreglarlo si quieres