Ich habe eine WPF-Combobox, die beispielsweise mit Kundenobjekten gefüllt ist. Ich habe eine DataTemplate:
<DataTemplate DataType="{x:Type MyAssembly:Customer}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Address}" />
</StackPanel>
</DataTemplate>
Auf diese Weise kann ich beim Öffnen meiner ComboBox die verschiedenen Kunden mit ihrem Namen und darunter der Adresse sehen.
Wenn ich jedoch einen Kunden auswähle, möchte ich nur den Namen in der ComboBox anzeigen. Etwas wie:
<DataTemplate DataType="{x:Type MyAssembly:Customer}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
Kann ich eine andere Vorlage für das ausgewählte Element in einer ComboBox auswählen?
Lösung
Mit Hilfe der Antworten habe ich es so gelöst:
<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>
Dann meine ComboBox:
<ComboBox ItemsSource="{Binding Customers}"
SelectedItem="{Binding SelectedCustomer}"
ItemTemplate="{StaticResource CustomerTemplate}" />
Der wichtige Teil, um es zum Laufen zu bringen, war Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}"
(der Teil, in dem der Wert x: Null sein sollte, nicht True).
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')
Antworten:
Es gibt zwei Probleme bei der Verwendung der oben genannten DataTrigger / Binding-Lösung. Das erste ist, dass Sie tatsächlich eine verbindliche Warnung erhalten, dass Sie die relative Quelle für das ausgewählte Element nicht finden können. Das größere Problem ist jedoch, dass Sie Ihre Datenvorlagen überfüllt und für eine ComboBox spezifisch gemacht haben.
Die Lösung, die ich vorstelle, folgt besser den WPF-Entwürfen, indem sie eine verwendet,
DataTemplateSelector
für die Sie separate Vorlagen mit ihren EigenschaftenSelectedItemTemplate
undDropDownItemsTemplate
Eigenschaften sowie Auswahlvarianten für beide angeben können .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); } }
return inDropDown ? DropdownItemsTemplate ?? ((DropdownItemsTemplateSelector != null) ? DropdownItemsTemplateSelector.SelectTemplate(item, container) : null) : SelectedItemTemplate ?? ((SelectedItemTemplateSelector != null) ? SelectedItemTemplateSelector.SelectTemplate(item, container) : null)
Ich habe auch eine Markup-Erweiterung eingefügt, die die obige Klasse einfach erstellt und zur Vereinfachung in XAML zurückgibt.
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 }; } }
Und so verwenden Sie es. Schön, sauber und klar und Ihre Vorlagen bleiben "rein"
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Items}" ItemTemplateSelector="{is:ComboBoxTemplateSelector SelectedItemTemplate={StaticResource MySelectedItemTemplate}, DropdownItemsTemplate={StaticResource MyDropDownItemTemplate}}" />
Sie können auch DataTemplateSelectors verwenden, wenn Sie ...
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Items}" ItemTemplateSelector="{is:ComboBoxTemplateSelector SelectedItemTemplateSelector={StaticResource MySelectedItemTemplateSelector}, DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Oder mischen und anpassen! Hier verwende ich eine Vorlage für das ausgewählte Element, aber eine Vorlagenauswahl für die DropDown-Elemente.
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Items}" ItemTemplateSelector="{is:ComboBoxTemplateSelector SelectedItemTemplate={StaticResource MySelectedItemTemplate}, DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Wenn Sie für die ausgewählten Elemente oder Dropdown-Elemente keine Vorlage oder keinen TemplateSelector angeben, wird einfach wie erwartet wieder auf die reguläre Auflösung von Datenvorlagen basierend auf Datentypen zurückgegriffen. So wird beispielsweise im folgenden Fall die Vorlage des ausgewählten Elements explizit festgelegt, aber die Dropdown-Liste erbt die Datenvorlage, die für den Datentyp des Objekts im Datenkontext gilt.
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Items}" ItemTemplateSelector="{is:ComboBoxTemplateSelector SelectedItemTemplate={StaticResource MyTemplate} />
Genießen!
quelle
return inDropDown
oben) das neue C # 6? Wenn Sie VS 2015 nicht verwenden, entfernen Sie einfach das '?' und vor dem Aufruf explizit auf Nullen prüfenSelectTemplate
. Ich werde das dem Code hinzufügen.Einfache Lösung:
<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>
(Beachten Sie, dass sich das Element, das im Feld ausgewählt und angezeigt wird, und nicht die Liste nicht in einem befindet,
ComboBoxItem
daher ist der Auslöser aktiviert.Null
)Wenn Sie die gesamte Vorlage austauschen möchten, können Sie dies auch tun, indem Sie den Auslöser verwenden, um z. B. eine andere
ContentTemplate
auf a anzuwendenContentControl
. Auf diese Weise können Sie auch eine standardbasierteDataType
Vorlagenauswahl beibehalten, wenn Sie nur die Vorlage für diesen ausgewählten Fall ändern, z.<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>
Beachten Sie, dass diese Methode Bindungsfehler verursacht, da die relative Quelle für das ausgewählte Element nicht gefunden wird. Für einen alternativen Ansatz siehe die Antwort von MarqueIV .
quelle
IsSelected
ist nicht nullbar und kann daher niemals wirklich NULL sein. Sie brauchen nichtPath=IsSelected
, weil die NULL-Prüfung für ein umgebendes ComboBoxItem völlig ausreichend ist.Artiom
Ansatz? (Wie Sie erwähnenShortName
.)Ich wollte vorschlagen, die Kombination einer ItemTemplate für die Kombinationselemente mit dem Textparameter als Titelauswahl zu verwenden, aber ich sehe, dass ComboBox den Textparameter nicht berücksichtigt.
Ich habe mich mit etwas Ähnlichem befasst, indem ich die ComboBox ControlTemplate überschrieben habe. Hier ist die MSDN- Website mit einem Beispiel für .NET 4.0.
In meiner Lösung ändere ich den ContentPresenter in der ComboBox-Vorlage so, dass er an Text gebunden wird, wobei die ContentTemplate an eine einfache DataTemplate gebunden ist, die einen TextBlock wie folgt enthält:
<DataTemplate x:Uid="DataTemplate_1" x:Key="ComboSelectionBoxTemplate"> <TextBlock x:Uid="TextBlock_1" Text="{Binding}" /> </DataTemplate>
damit in der ControlTemplate:
<ContentPresenter Name="ContentSite" IsHitTestVisible="False" Content="{TemplateBinding Text}" ContentTemplate="{StaticResource ComboSelectionBoxTemplate}" Margin="3,3,23,3" VerticalAlignment="Center" HorizontalAlignment="Left"/>
Mit diesem Bindungslink kann ich die Anzeige der Kombinationsauswahl direkt über den Parameter Text auf dem Steuerelement steuern (den ich an einen geeigneten Wert in meinem ViewModel binde).
quelle
Ich habe den nächsten Ansatz gewählt
<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>
Und das Verhalten
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; } }
Lief wie am Schnürchen. Ich mag das geladene Ereignis hier nicht so sehr, aber Sie können es beheben, wenn Sie möchten
quelle
Zusätzlich zu den Antworten von HB kann der Bindungsfehler mit einem Konverter vermieden werden. Das folgende Beispiel basiert auf der vom OP selbst bearbeiteten Lösung .
Die Idee ist sehr einfach: Binden Sie an etwas, das immer existiert (
Control
), und führen Sie die entsprechende Überprüfung im Konverter durch. Der relevante Teil der modifizierten XAML ist der folgende. Bitte beachten Sie, dass diesPath=IsSelected
nie wirklich benötigt wurde undComboBoxItem
durch ersetzt wirdControl
, um Bindungsfehler zu vermeiden.<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>
Der C # -Konvertercode lautet wie folgt:
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(); } }
quelle
Ja. Mit einer Vorlagenauswahl bestimmen Sie, welche Vorlage zur Laufzeit gebunden werden soll. Wenn also IsSelected = False ist, verwenden Sie diese Vorlage. Wenn IsSelected = True, verwenden Sie diese andere Vorlage.
Hinweis: Sobald Sie Ihre Vorlagenauswahl implementiert haben, müssen Sie den Vorlagen Schlüsselnamen geben.
quelle