Posso utilizzare un modello diverso per l’object selezionato in un ComboBox WPF rispetto agli elementi nella parte a discesa?

Ho un Combobox WPF che è pieno di oggetti del cliente, ad esempio. Ho un DataTemplate:

      

In questo modo, quando apro il mio ComboBox, posso vedere i diversi clienti con il loro nome e, di seguito, l’indirizzo.

Ma quando seleziono un cliente, voglio solo visualizzare il nome nel ComboBox. Qualcosa di simile a:

      

Posso selezionare un altro modello per l’object selezionato in un ComboBox?

Soluzione

Con l’aiuto delle risposte, l’ho risolto in questo modo:

                      

Quindi, il mio ComboBox:

  

La parte importante per farlo funzionare era Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}" (la parte dove il valore dovrebbe essere x: Null, non True).

Il problema con l’utilizzo della soluzione DataTrigger / Binding sopra menzionata è duplice. Il primo è che in effetti si finisce con un avviso di associazione che non è ansible trovare la fonte relativa per l’elemento selezionato. Il problema più grande tuttavia è che hai ingombrato i tuoi modelli di dati e li hai resi specifici per un ComboBox.

La soluzione che presento segue i progetti WPF in quanto utilizza un DataTemplateSelector su cui è ansible specificare modelli separati utilizzando le proprietà SelectedItemTemplate e DropDownItemsTemplate nonché i selettori per entrambi.

 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: per semplicità, il mio codice di esempio qui utilizza il nuovo ‘?.’ funzionalità di C # 6 (VS 2015). Se stai utilizzando una versione precedente, rimuovi semplicemente “?” e verificare esplicitamente il null prima di chiamare ‘SelectTemplate’ in alto e restituire null altrimenti in questo modo:

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

Ho anche incluso un’estensione di markup che semplicemente crea e restituisce la class sopra per praticità in 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 }; } } 

Ed ecco come lo usi. Bello, pulito e chiaro e i tuoi modelli rimangono “puri”

Nota: ‘is:’ ecco la mia mapping xmlns per dove ho inserito la class nel codice. Assicurati di importare il tuo spazio dei nomi e cambia “è:” come appropriato.

  

Puoi anche utilizzare DataTemplateSelectors se preferisci …

  

O mescolare e abbinare! Qui sto usando un modello per l’elemento selezionato, ma un selettore modello per gli oggetti DropDown.

  

Inoltre, se non si specifica un modello o un TemplateSelector per gli elementi selezionati o a discesa, si ricorre semplicemente alla normale risoluzione dei modelli di dati in base ai tipi di dati, sempre, come ci si aspetterebbe. Quindi, ad esempio, nel caso seguente, l’elemento selezionato ha il suo modello impostato in modo esplicito, ma il menu a discesa erediterà qualsiasi modello di dati applicato per il DataType dell’object nel contesto dei dati.

 
		      	

Soluzione semplice:

           

(Si noti che l’elemento selezionato e visualizzato nella casella e non nell’elenco non è all’interno di un object ComboBoxItem quindi il trigger su Null )

Se si desidera cambiare l’intero modello, è ansible farlo anche utilizzando il trigger per applicare ad esempio un ContentTemplate diverso a ContentControl . Ciò consente anche di mantenere una selezione predefinita del modello DataType se si modifica semplicemente il modello per questo caso selettivo, ad esempio:

          

Si noti che questo metodo causerà errori di associazione poiché la relativa origine non viene trovata per l’elemento selezionato. Per un approccio alternativo vedi la risposta di MarqueIV .

Stavo per suggerire di utilizzare la combinazione di un ItemTemplate per gli elementi combinati, con il parametro Text come selezione del titolo, ma vedo che ComboBox non rispetta il parametro Text.

Mi sono occupato di qualcosa di simile sovrascrivendo il ComboBox ControlTemplate. Ecco il sito Web MSDN con un esempio per .NET 4.0.

Nella mia soluzione, cambio ContentPresenter nel modello ComboBox per legarlo a Text, con ContentTemplate associato a un DataTemplate semplice che contiene un TextBlock in questo modo:

    

con questo nel ControlTemplate:

  

Con questo collegamento vincolante, sono in grado di controllare il display di selezione Combo direttamente tramite il parametro Text sul controllo (che lego a un valore appropriato sul mio ViewModel).

Ho usato il prossimo approccio

            

E il comportamento

 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(uiElement); if (contentPresenter == null) return; var template = uiElement.GetSelectedItemDataTemplate(); contentPresenter.ContentTemplate = template; } public static T GetChildOfType(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(child); if (result != null) return result; } return null; } } 

ha funzionato come un fascino. Non ti piace l’evento Loaded più o meno qui ma puoi aggiustarlo se vuoi

Sì. Si utilizza un Selettore modelli per determinare quale modello bind in fase di esecuzione. Pertanto, se IsSelected = False, utilizza questo modello, se IsSelected = True, utilizza questo altro modello.

Nota: una volta implementato il selettore modello, sarà necessario fornire i nomi chiave dei modelli.

Oltre a quanto detto dalla risposta HB , l’errore di associazione può essere evitato con un convertitore. Il seguente esempio è basato sulla Soluzione edita dall’OP stesso .

L’idea è molto semplice: bind a qualcosa che esiste sempre ( Control ) e fare il controllo pertinente all’interno del convertitore. La parte rilevante dello XAML modificato è la seguente. Nota: Path=IsSelected non è mai stato realmente necessario e ComboBoxItem viene sostituito da Control per evitare errori di associazione.

    

Il codice del convertitore C # è il seguente:

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