Problem z użyciem wspomnianego wyżej rozwiązania DataTrigger/Binding jest dwojaki. Pierwszy z nich kończy się ostrzeżeniem wiążącym, że nie można znaleźć źródła względnego dla wybranego elementu. Większym problemem jest jednak bałagan w szablonach danych i dostosowywanie ich do ComboBox.
Rozwiązanie, które prezentuję lepiej po projektach WPF, polega na tym, że używa DataTemplateSelector, na którym można określić oddzielne szablony przy użyciu właściwości SelectedItemTemplate i DropDownItemsTemplate, a także selektorów dla obu.
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);
}
}
Note: For simplicity, my example code here uses the new '?.' feature of C#6 (VS 2015). If you're using an older version, simply remove the '?' and explicitly check for null before calling 'SelectTemplate' above and return null otherwise like so:
return inDropDown
? DropdownItemsTemplate ??
((DropdownItemsTemplateSelector != null)
? DropdownItemsTemplateSelector.SelectTemplate(item, container)
: null)
: SelectedItemTemplate ??
((SelectedItemTemplateSelector != null)
? SelectedItemTemplateSelector.SelectTemplate(item, container)
: null)
Mam również rozszerzenie znaczników, które po prostu Tworzy i zwraca powyższą klasę dla wygody w 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
};
}
}
A oto jak z niego korzystać. Ładne, czyste i jasne i szablony pozostać 'czysta'
Note: 'is:' here is my xmlns mapping for where I put the class in code. Make sure to import your own namespace and change 'is:' as appropriate.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MySelectedItemTemplate},
DropdownItemsTemplate={StaticResource MyDropDownItemTemplate}}" />
Można również użyć DataTemplateSelectors jeśli wolisz ...
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplateSelector={StaticResource MySelectedItemTemplateSelector},
DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Albo łączyć! Tutaj używam szablonu dla wybranego elementu, ale selektora szablonu dla elementów DropDown.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MySelectedItemTemplate},
DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Dodatkowo, jeśli nie zostanie określony szablon lub TemplateSelector dla wybranych lub rozwijanych elementów, po prostu wraca do regularnego rozwiązywania szablonów danych na podstawie typów danych, znowu, jak można by oczekiwać. Na przykład w poniższym przypadku wybrany element ma swój szablon jawnie ustawiony, ale lista rozwijana odziedziczy dowolny szablon danych dla obiektu DataType obiektu w kontekście danych.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MyTemplate} />
Ciesz się!
Twoje rozwiązanie działa, ale pojawiają się błędy w oknie Output. 'System.Windows.Błąd danych: 4: Nie można znaleźć źródła dla wiązania z odniesieniem "RelativeSource FindAncestor, AncestorType = 'System.Windows.Controls.ComboBoxItem', AncestorLevel = '1' '. BindingExpression: Path = IsSelected; DataItem = null; elementem docelowym jest 'ContentPresenter' (Name = ''); właściwością target jest "NoTarget" (type 'Object') ' – user2190035
Pamiętam, że widziałem również te błędy. Ale nie jestem już w projekcie (ani nawet w firmie), więc nie mogę tego sprawdzić, przepraszam. – Peter