programing

WPF ComboBox에서 선택한 항목에 드롭다운 부분의 항목과 다른 템플릿을 사용할 수 있습니까?

abcjava 2023. 4. 16. 14:17
반응형

WPF ComboBox에서 선택한 항목에 드롭다운 부분의 항목과 다른 템플릿을 사용할 수 있습니까?

WPF 콤보박스는 고객님의 오브젝트로 채워져 있습니다.Data Template가 있습니다.

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

이렇게 하면 ComboBox를 열면 다른 고객의 이름과 그 아래에 주소가 표시됩니다.

단, Customer를 선택하면 ComboBox에 이름만 표시됩니다.예를 들어 다음과 같습니다.

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

ComboBox에서 선택한 항목에 대해 다른 템플릿을 선택할 수 있습니까?

솔루션

답변의 도움을 받아 다음과 같이 해결했습니다.

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

그러면 내 ComboBox:

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

작동시키기 위한 중요한 부분은Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}"('True') (x: Null) ('True')

위에서 설명한 DataTrigger/Binding 솔루션 사용의 문제는 두 가지입니다.첫 번째는 선택한 항목의 상대 소스를 찾을 수 없다는 바인딩 경고입니다.그러나 더 큰 문제는 데이터 템플릿을 뒤죽박죽으로 만들어 콤보박스 전용으로 만들었다는 것입니다.

WPF 설계에서는 WPF를 하고 있다는 에서 이 이 WPF 를 잘 .WPF 「」,DataTemplateSelector에는, 「」을 해 다른 템플릿을 지정할 수 있습니다.SelectedItemTemplate ★★★★★★★★★★★★★★★★★」DropDownItemsTemplate속성 및 두 가지 모두에 대한 '고유' 변형입니다.

주의: C#9 용으로 갱신되었으며, 무효가 유효하게 되어 있어 검색중에 패턴 매칭을 사용하고 있습니다.

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 is not null
        and not ComboBox
        and not ComboBoxItem)
            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); 
    }
}

XAML에서 사용할 수 있도록, 들 다 의 환 크 는 하 게 하 고 래 make to in its 해 기 클 included x class x in createsProvideValue기능.기능.

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)
        => new ComboBoxTemplateSelector(){
            SelectedItemTemplate          = SelectedItemTemplate,
            SelectedItemTemplateSelector  = SelectedItemTemplateSelector,
            DropdownItemsTemplate         = DropdownItemsTemplate,
            DropdownItemsTemplateSelector = DropdownItemsTemplateSelector
        };
}

사용법은 다음과 같습니다.깔끔하고 깨끗한 템플릿으로 '순수' 유지

주의: 'is:'는 클래스를 코드에 넣을 xmlns 매핑입니다.사용자 자신의 네임스페이스를 가져오고 필요에 따라 'is:'를 변경하십시오.

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

원하는 경우 DataTemplateSelectors를 사용할 수도 있습니다.

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

아니면 믹스 앤 매치!여기서는 선택한 항목에 대한 템플릿을 사용하고 있지만 드롭다운 항목에 대한 템플릿 선택기를 사용하고 있습니다.

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

또한 선택한 항목 또는 드롭다운 항목에 대해 Template Selector 또는 Template Selector를 지정하지 않으면 예상대로 데이터 유형에 따라 데이터 템플릿이 정기적으로 해결됩니다.예를 들어, 아래의 경우 선택한 항목의 템플릿은 명시적으로 설정되지만 드롭다운은 데이터 컨텍스트에서 개체의 DataType에 적용되는 데이터 템플릿을 상속합니다.

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

맛있게 드세요!

심플한 솔루션:

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

(목록이 아닌 상자에 선택 및 표시되는 요소는 이 안에 없습니다).ComboBoxItem따라서 방아쇠가 되다Null)

전체 템플릿을 전환하려면 트리거를 사용하여 에 다른 템플릿을 적용하는 방법도 있습니다.이를 통해 기본값을 유지할 수도 있습니다.DataType- 이 선택적인 케이스에 대한 템플릿을 변경하는 경우 다음과 같이 템플릿 선택:

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

이 메서드는 선택한 항목에 대한 상대 소스를 찾을 수 없으므로 바인딩 오류가 발생합니다.대체 접근법에 대해서는 Marque를 참조해 주십시오.IV의 답입니다.

H.B.의 답변에 덧붙여, Converter를 사용하면 Binding Error를 회피할 수 있습니다.다음 예는 OP가 직접 편집한 솔루션을 기반으로 합니다.

아이디어는 매우 간단합니다: 항상 존재하는 무언가에 구속됩니다.Control) 및 컨버터 내부의 관련 점검을 실시합니다.변경된 XAML의 관련 부분은 다음과 같습니다.주의하시기 바랍니다.Path=IsSelected전혀 필요없고ComboBoxItem로 대체되었습니다.Control바인드 오류를 회피합니다.

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

C# Converter 코드는 다음과 같습니다.

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

제목 선택으로 Text 파라미터를 사용하여 콤보 아이템에 ItemTemplate를 조합하는 것을 제안하려고 했는데, Text 파라미터가 ComboBox에 적용되지 않는 것 같습니다.

ComboBox Control Template를 덮어쓰고 비슷한 처리를 했습니다.이것은 MSDN 웹사이트와 샘플입니다.NET 4.0.

솔루션에서는 다음과 같이 ComboBox 템플릿의 ContentPresenter를 변경하여 텍스트에 바인드하고 ContentTemplate는 텍스트 블록을 포함하는 단순한 DataTemplate에 바인드합니다.

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

ControlTemplate에서 다음을 수행합니다.

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

이 바인딩 링크를 사용하면 컨트롤의 Text 매개 변수(ViewModel에서 적절한 값에 바인딩)를 통해 Combo 선택 표시를 직접 제어할 수 있습니다.

나는 다음 접근방식을 사용했다.

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

그리고 행동은

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

아주 잘 작동했어요.로드된 이벤트를 별로 좋아하지 않지만 원하는 경우 수정할 수 있습니다.

예. 템플릿 선택기를 사용하여 런타임에 바인딩할 템플릿을 결정합니다.따라서 IsSelected = False이면 Use this template, IsSelected = True이면 이 다른 템플릿을 사용합니다.

주의사항:템플릿 셀렉터를 구현하면 템플릿에 키 이름을 지정해야 합니다.

I propose this solution without 저는 이 해결책을 제안합니다.DataTemplateSelector,Trigger,binding nor 도 아니다behavior.

The first step is to put the 첫 번째 순서는, 「Diaguar를ItemTemplate(선택사항)에서 선택된 요소 중)ComboBox 및 "" " " " "ItemTemplate의 항목에 )에서ComboBox.ItemsPanel리소스에 동일한 키를 부여합니다.

입니다.ItemTemplate 시 :ContentPresenter a. a. a.DynamicResourceComboBox.ItemTemplate★★★★★★ 。

<ComboBox ItemsSource="{Binding Items, Mode=OneWay}">

    <ComboBox.Resources>
        <!-- Define ItemTemplate resource -->
        <DataTemplate x:Key="ItemTemplate" DataType="viewModel:ItemType">
            <TextBlock Text="{Binding FieldOne, Mode=OneWay}" />
        </DataTemplate>
    </ComboBox.Resources>

    <ComboBox.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Grid.IsSharedSizeScope="True"
                        IsItemsHost="True">
                <StackPanel.Resources>
                    <!-- Redefine ItemTemplate resource -->
                    <DataTemplate x:Key="ItemTemplate" DataType="viewModel:ItemType">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" SharedSizeGroup="GroupOne" />
                                <ColumnDefinition Width="10" SharedSizeGroup="GroupSpace" />
                                <ColumnDefinition Width="Auto" SharedSizeGroup="GroupTwo" />
                            </Grid.ColumnDefinitions>
                
                            <TextBlock Grid.Column="0" Text="{Binding FieldOne, Mode=OneWay}" />
                            <TextBlock Grid.Column="2" Text="{Binding FieldTwo, Mode=OneWay}" />
                        </Grid>
                    </DataTemplate>
                </StackPanel.Resources>
            </StackPanel>
        </ItemsPanelTemplate>
    </ComboBox.ItemsPanel>

    <ComboBox.ItemTemplate>
        <DataTemplate>
            <ContentPresenter ContentTemplate="{DynamicResource ItemTemplate}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

언급URL : https://stackoverflow.com/questions/4672867/can-i-use-a-different-template-for-the-selected-item-in-a-wpf-combobox-than-for

반응형