2011-01-12 12 views
42

Müşteri nesneleri ile doldurulan bir WPF Combobox'ım var. Benim ComboBox açtığınızdaBir WPF ComboBox'ta seçilen öğe için açılır menüdeki öğelerden farklı bir Şablon kullanabilir miyim?

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

Bu şekilde, ben, bunun altında Adres onların Adı farklı müşteriler görebilir ve: Ben bir DataTemplate var.

Ancak bir Müşteri seçtiğimde, yalnızca Adı Açılan Kutusunda görüntülemek istiyorum. Şunlar gibi:

Bir ComboBox'ta seçilen öğe için başka bir Şablon seçebilir miyim?

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

, benim ComboBox:

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

önemli bir parçası almak için işe cevaplar yardımıyla

Çözüm, ben böyle çözülür Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}" (değerin x olması gereken kısımdı: Boş değil, Boş).

+1

Çözümünüz çalışıyor, ancak Çıktı penceresinde hatalar alıyorum. 'System.Windows.Veri Hatası: 4: Referans ile bağlama için kaynak bulunamadı 'RelativeSource FindAncestor, AncestorType =' System.Windows.Controls.ComboBoxItem ', AncestorLevel =' 1 ''. BindingExpression: Yol = IsSelected; Dataıtem = null; hedef öğe 'ContentPresenter' (Name = ''); target özelliği 'NoTarget' ('Object') ' – user2190035

+1

'dır Bu hataları da gördüğümü hatırlıyorum. Ama artık projede (hatta şirkette) değilim, bu yüzden kontrol edemem özür dilerim. – Peter

cevap

31

Yukarıda belirtilen DataTrigger/Binding çözümünün kullanılması sorunu çözebilirsiniz. Birincisi, seçilen öğenin göreceli kaynağını bulamadığınız bir bağlanma uyarısıyla sonuçlanırsınız. Bununla birlikte, daha büyük sorun, veri şablonlarınızı karıştırmanız ve bunları bir ComboBox'a özel hale getirmenizdir.

WPF tasarımlarını daha iyi sunduğum çözüm, SelectedItemTemplate ve DropDownItemsTemplate özelliklerini ve her ikisi için de Seçici'yi kullanarak ayrı şablonlar belirleyebileceğiniz bir DataTemplateSelector kullanmasıdır.

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) 

Ben de basitçe oluşturur ve XAML kolaylık için yukarıdaki sınıfını döndüren bir biçimlendirme uzantısı dahil ettik.

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

Ve işte bunları nasıl kullanıyorsunuz. Güzel, temiz ve berrak ve Şablonlarınız kalmak 'saf'

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}}" /> 
İsterseniz de DataTemplateSelectors kullanabilirsiniz

...

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

Veya karıştırıp maç! Burada seçili öğe için bir şablon kullanıyorum, ancak DropDown öğeleri için bir şablon seçici kullanıyorum. Bir Şablon veya seçilen veya açılır öğeler için bir TemplateSelector belirtmek yoksa beklediğiniz gibi

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

Ayrıca, basitçe, tekrar veri türlerine bağlı olarak veri şablonları çözme düzenli düşer. Bu nedenle, örneğin, aşağıdaki durumda, seçilen öğenin şablonu açıkça ayarlanır, ancak açılır menüde, veri bağlamındaki nesnenin DataType'ı için hangi veri şablonu geçerli olur.

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

+0

Çok güzel. Ve gerçekten de bu bağlayıcı uyarılara sahibim (asla nereden geldiklerini anlamadım, ama gerçekten de bir bakmadım). Şu anda gerçekten kontrol edebilirim, ama gelecekte olabilirim. – Peter

+0

Yardım almaktan memnun oldum. Bunu kodunuzda kullanıp kullanmadığınızı bildiğinizde, return ifadesi ('yukarıdaki inDropDown ') yeni C# 6'yı kullanır. sentaks yani VS 2015 kullanmıyorsan, sadece '?' ve 'SelectTemplate' çağırılmadan önce null'ları açıkça kontrol edin. Bunu koda ekleyeceğim. – MarqueIV

+3

Gerçekten yeniden kullanılabilir bir çözüm için şapkamı elime alıyorum! – henon

0

Evet. Çalışma zamanında hangi şablona bağlanacağını belirlemek için Template Selector kullanın. Bu nedenle, IsSelected = False ise, bu şablonu kullanın, IsSelected = True ise, bu diğer şablonu kullanın.

Not: Şablon seçicinizi uyguladıktan sonra, şablon anahtar sözcüklerini vermeniz gerekir.

+0

Bunu da denedim, burada da bulunduğum örnekleri kullanarak (http://www.developingfor.net/net/dynamically-switch-wpf-datatemplate.html), ancak çok tekrar kullanılamaz ve bunu XAML'de çözmek istedim. bir tek. – Peter

28

Basit çözüm:

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

(seçilip kutusunda görüntülenir ve eleman değil liste dolayısıyla ComboBoxItemNull üzerinde tetikleyici içeride olmadığını unutmayın) Eğer Tetikleyiciyi kullanarak tüm şablonu değiştirebilir, örneğin apply a different ContentTemplate to a ContentControl. Bu aynı zamanda sadece bu seçici durumda şablonunu değiştirmek eğer varsayılan bir DataType tabanlı şablon seçimini korumak için izin verir, örneğin: göreli kaynak için bulunmazsa olarak bu yöntem bağlama hatalara neden olacağı

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

Not seçilen öğe. Alternatif bir yaklaşım için bkz. MarqueIV's answer.

+0

Ayrı tutmak için iki Şablon kullanmak istedim. Bu siteden örnek bir projeden kod kullandım: http://www.developingfor.net/net/dynamically-switch-wpf-datatemplate.html. Ancak bir ListBox için çalışırken, bir ComboBox için işe yaramadı. Son cümlenin onu ya da ben çözdüm. Bir ComboBox'ta seçilen öğe IsSelected = True değil, ancak boş. Tam düzenlemem için yukarıdaki düzenlememe nasıl çözdüğümü görün. Çok teşekkürler! – Peter

+0

İstediğiniz tam olarak olmasa bile kullanışlı olduğunu sevindim. Sorunuzu cevaplamaya çalışmadan önce null-şeyini bilmiyordum, denedim ve bu şekilde öğrendim. –

+4

'IsSelected', null değil ve bu yüzden asla NULL olamaz. Çevrenizdeki bir ComboBoxItem için NULL denetimi tamamen yeterli olduğundan, 'Path = IsSelected' seçeneğine ihtiyacınız yoktur. – springy76

1

Combo items için ComboBox'ın Text parametresine uymadığını görüyorum, combo öğeleri için bir CommonsTemplate kombinasyonu kullanmayı önerecektim.

ComboBox ControlTemplate'i geçersiz kılarak benzer bir şeyle uğraştım. İşte .NET 4.0 için bir örnek ile MSDN website.ControlTemplate bu ile

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

: onun ContentTemplate bir şöyle TextBlock içeren basit DataTemplate bağlı olan

benim çözümde, Bence bu metin bağlamak için ComboBox şablonda ContentPresenter değiştirmek Bu bağlanma bağlantısı sayesinde

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

, ben (benim ViewModel üzerinde uygun bir değere bağlamak) kumanda üzerindeki metin parametresi üzerinden doğrudan Combo seçim ekranı kontrol etmek mümkün.

+0

Tam olarak aradığım şey bu değil. Sadece bir parça metin göstermek için 'aktif' olmayan bir ComboBox'un (yani kullanıcı üzerinde tıklanmadı, 'açık' değil) görünümünü istiyorum. Ancak, kullanıcı bunu tıkladığında aç/açılır ve her öğe iki parça metin göstermelidir (böylece farklı bir şablon). – Peter

+0

Yukarıdaki kodu denerseniz, gitmek istediğiniz yere ulaşacağınızı düşünüyorum. Bu kontrol şablonunu ayarlayarak, Combo'nun daraltılmış metnini Text özelliğiyle (veya istediğiniz mülkünüzle) kontrol edebilirsiniz, böylece basit seçilmemiş metninizi görüntüleyebilirsiniz. Combobox'ınızı oluştururken ItemTemplate öğesini belirterek tek tek öğe metinlerini değiştirebilirsiniz. (ItemTemplate muhtemelen bir stackpanel ve iki TextBlocks, ya da hoşunuza giden ne olursa olsun olurdu.) – cunningdave

1

sonraki yaklaşım

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

Ve

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

harika çalıştı davranışı kullanılır. Çok fazla Yüklenen olaydan hoşlanmayın ama isterseniz

İlgili konular