2011-01-12 21 views
42

Tôi có một Combobox WPF được lấp đầy với, nói rằng, đối tượng khách hàng. Tôi có một DataTemplate:Tôi có thể sử dụng Mẫu khác cho mục đã chọn trong ComboBox WPF hơn là các mục trong phần thả xuống không?

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

Bằng cách này, khi tôi mở ComboBox, tôi có thể thấy các Khách hàng khác nhau với Tên của họ và, bên dưới là Địa chỉ.

Nhưng khi tôi chọn Khách hàng, tôi chỉ muốn hiển thị Tên trong ComboBox. Một cái gì đó như:

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

Tôi có thể chọn Mẫu khác cho mục đã chọn trong ComboBox không?

Giải pháp

Với sự giúp đỡ từ các câu trả lời, tôi giải quyết nó như thế này:

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

Sau đó, ComboBox của tôi:

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

Phần quan trọng để làm cho nó làm việc là Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}" (phần giá trị phải là x: Null, không đúng).

+1

Giải pháp của bạn hoạt động, nhưng tôi gặp lỗi trong cửa sổ Kết quả. 'System.Windows.Lỗi dữ liệu: 4: Không thể tìm thấy nguồn để liên kết với tham chiếu 'RelativeSource FindAncestor, AncestorType =' System.Windows.Controls.ComboBoxItem ', AncestorLevel =' 1 ''. BindingExpression: Đường dẫn = IsSelected; DataItem = null; phần tử đích là 'ContentPresenter' (Tên = ''); thuộc tính đích là 'NoTarget' (gõ 'Object') ' – user2190035

+1

Tôi nhớ cũng thấy các lỗi này. Nhưng tôi không còn trong dự án (hoặc thậm chí trong công ty), vì vậy tôi không thể kiểm tra điều này, xin lỗi. – Peter

Trả lời

31

Vấn đề với việc sử dụng giải pháp DataTrigger/Binding được đề cập ở trên là hai lần. Đầu tiên là bạn thực sự kết thúc với một cảnh báo ràng buộc rằng bạn không thể tìm thấy nguồn tương đối cho mục đã chọn. Vấn đề lớn hơn tuy nhiên là bạn đã lộn xộn lên các mẫu dữ liệu của bạn và làm cho chúng cụ thể cho một ComboBox.

Giải pháp tôi trình bày tốt hơn theo thiết kế WPF ở chỗ nó sử dụng một DataTemplateSelector mà bạn có thể chỉ định các mẫu riêng biệt bằng cách sử dụng thuộc tính SelectedItemTemplate và DropDownItemsTemplate cũng như Selectors cho cả hai.

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) 

Tôi cũng đã bao gồm một phần mở rộng đánh dấu mà chỉ đơn giản tạo ra và trả về lớp trên để thuận tiện trong 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 
     }; 
    } 
} 

Và dưới đây là cách bạn sử dụng. Thoải mái, sạch sẽ và rõ ràng và các mẫu của bạn ở lại 'tinh khiết'

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}}" /> 

Bạn cũng có thể sử dụng DataTemplateSelectors nếu bạn thích ...

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

Hoặc trộn và kết hợp! Ở đây tôi đang sử dụng một mẫu cho mục đã chọn, nhưng một bộ chọn mẫu cho các mục DropDown.

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

Ngoài ra, nếu bạn không chỉ định một mẫu hoặc một TemplateSelector cho các mục đã chọn hoặc thả xuống, nó chỉ đơn giản là rơi trở lại thường xuyên giải quyết các mẫu dữ liệu dựa trên các kiểu dữ liệu, một lần nữa, như bạn mong đợi. Vì vậy, ví dụ, trong trường hợp dưới đây, mục đã chọn có khuôn mẫu được đặt rõ ràng, nhưng trình đơn thả xuống sẽ kế thừa bất kỳ mẫu dữ liệu nào áp dụng cho DataType của đối tượng trong ngữ cảnh dữ liệu.

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

Tận hưởng!

+0

Rất hay. Và tôi thực sự có những cảnh báo ràng buộc đó (không bao giờ phát hiện ra chúng xuất phát từ đâu, nhưng cũng không thực sự xem xét). Tôi thực sự có thể kiểm tra nó ngay bây giờ, nhưng tôi có thể trong tương lai. – Peter

+0

Rất vui được hỗ trợ. Chỉ cần biết nếu bạn đang sử dụng điều này trong mã của bạn, câu lệnh return ('return inDropDown' ở trên) sử dụng C# 6 mới. cú pháp vì vậy nếu bạn không sử dụng VS 2015, chỉ cần xóa '?' và kiểm tra rõ ràng các giá trị rỗng trước khi gọi 'SelectTemplate'. Tôi sẽ thêm nó vào mã. – MarqueIV

+3

Tôi lấy mũ của tôi ra cho bạn một giải pháp thực sự có thể tái sử dụng! – henon

0

Có. Bạn sử dụng một số Template Selector để xác định mẫu nào sẽ liên kết tại thời gian chạy. Vì vậy, nếu IsSelected = False sau đó sử dụng mẫu này, nếu IsSelected = True, sử dụng mẫu khác này.

Lưu ý: Khi bạn triển khai công cụ chọn mẫu, bạn cần phải cung cấp tên khóa mẫu.

+0

Tôi đã thử rằng, bằng cách sử dụng các ví dụ tôi cũng tìm thấy ở đây (http://www.developingfor.net/net/dynamically-switch-wpf-datatemplate.html), nhưng thấy nó không tái sử dụng, và tôi muốn giải quyết điều này trong XAML chỉ có. – Peter

28

Giải pháp đơn giản:

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

(Lưu ý rằng các yếu tố đó được chọn và hiển thị trong hộp và không trong danh sách không phải là bên trong một ComboBoxItem do đó kích hoạt trên Null)

Nếu bạn muốn chuyển toàn bộ khuôn mẫu, bạn có thể thực hiện điều đó bằng cách sử dụng trình kích hoạt để ví dụ apply a different ContentTemplate to a ContentControl. Điều này cũng cho phép bạn giữ lại một mặc định DataType dựa trên mẫu lựa chọn nếu bạn chỉ cần thay đổi mẫu đối với trường hợp có chọn lọc này, ví dụ:

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

Lưu ý rằng phương pháp này sẽ gây ra lỗi ràng buộc như là nguồn tương đối không được tìm thấy cho chọn mục. Để biết cách tiếp cận thay thế, hãy xem MarqueIV's answer.

+0

Tôi muốn sử dụng hai Mẫu, để giữ nó riêng biệt. Tôi đã sử dụng mã từ một dự án mẫu từ trang web này: http://www.developingfor.net/net/dynamically-switch-wpf-datatemplate.html. Nhưng trong khi nó làm việc cho một ListBox, nó không làm việc cho một ComboBox. Câu cuối cùng của bạn giải quyết nó hoặc tôi mặc dù. Mục được chọn trong ComboBox không có IsSelected = True, nhưng nó là Null. Xem chỉnh sửa của tôi ở trên để biết mã đầy đủ cách tôi đã giải quyết. Cảm ơn rất nhiều! – Peter

+0

Rất vui vì nó hữu ích mặc dù nó không chính xác những gì bạn yêu cầu. Tôi không biết về điều vô dụng trước khi cố trả lời câu hỏi của bạn, tôi đã thử nghiệm và phát hiện ra nó theo cách đó. –

+4

'IsSelected' không phải là nullable và do đó không bao giờ có thể thực sự NULL. Bạn không cần 'Path = IsSelected', bởi vì kiểm tra NULL cho một ComboBoxItem xung quanh là hoàn toàn đủ. – springy76

1

Tôi sẽ đề xuất sử dụng kết hợp một ItemTemplate cho các mục kết hợp, với tham số Văn bản làm tiêu đề lựa chọn, nhưng tôi thấy rằng ComboBox không tôn trọng tham số Văn bản.

Tôi xử lý điều gì đó tương tự bằng cách ghi đè ComboBox ControlTemplate. Đây là MSDN website với mẫu cho .NET 4.0.

Trong giải pháp của tôi, tôi thay đổi ContentPresenter trong mẫu ComboBox để có nó liên kết với văn bản, với ContentTemplate nó ràng buộc với một DataTemplate đơn giản có chứa một TextBlock như vậy:

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

với điều này trong ControlTemplate :

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

Với liên kết ràng buộc này, tôi có thể điều khiển hiển thị lựa chọn kết hợp trực tiếp thông số Text trên điều khiển (mà tôi liên kết với giá trị thích hợp trên ViewModel).

+0

Không hoàn toàn chắc chắn đây là những gì tôi đang tìm kiếm. Tôi muốn giao diện của một ComboBox không phải là 'hoạt động' (tức là người dùng chưa nhấp vào nó, nó không phải là 'mở'), để chỉ hiển thị một đoạn văn bản. Nhưng sau đó, khi người dùng nhấp vào nó, nó sẽ mở/thả xuống và mọi mục sẽ hiển thị hai phần văn bản (do đó, một mẫu khác). – Peter

+0

Nếu bạn thử nghiệm với mã ở trên, tôi nghĩ bạn sẽ đến nơi bạn muốn đến. Bằng cách thiết lập mẫu điều khiển này, bạn có thể kiểm soát văn bản thu gọn của combo thông qua thuộc tính Text (hoặc bất kỳ thuộc tính nào bạn thích), do đó cho phép bạn hiển thị văn bản không được chọn đơn giản của bạn. Bạn có thể sửa đổi các văn bản của từng mục bằng cách chỉ định ItemTemplate khi bạn tạo combobox của mình. (ItemTemplate có lẽ sẽ có một stackpanel và hai TextBlocks, hoặc bất kỳ hình dạng nào bạn thích.) – cunningdave

1

tôi đã sử dụng phương pháp tiếp theo

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

Và hành vi

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

làm việc như một nét duyên dáng. Không thích nhiều sự kiện được tải ở đây nhưng bạn có thể khắc phục sự cố nếu bạn muốn

Các vấn đề liên quan