Могу ли я использовать другой шаблон для выбранного элемента в поле со списком WPF, чем для элементов в раскрывающейся части? - PullRequest
60 голосов
/ 12 января 2011

У меня есть WPF Combobox, который заполнен, скажем, объектами Customer. У меня есть DataTemplate:

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

Таким образом, когда я открываю свой ComboBox, я вижу разных клиентов с их именем и, под этим, адресом.

Но когда я выбираю Клиента, я хочу отображать только Имя в ComboBox. Что-то вроде:

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

Можно ли выбрать другой шаблон для выбранного элемента в поле со списком?

Решение

С помощью ответов я решил это так:

<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}" (часть, где значение должно быть x: Нуль, не Истина).

Ответы [ 6 ]

53 голосов
/ 29 октября 2015

Проблема с использованием решения DataTrigger / Binding, упомянутого выше, имеет две стороны.Во-первых, вы фактически получаете связующее предупреждение о том, что не можете найти относительный источник для выбранного элемента.Однако большая проблема заключается в том, что вы загромождали свои шаблоны данных и делали их специфичными для ComboBox.

Решение, которое я представляю, лучше соответствует проектам WPF в том смысле, что в нем используется DataTemplateSelector, для которого можно указать отдельныешаблоны, использующие его свойства SelectedItemTemplate и DropDownItemsTemplate, а также варианты «селектора» для обоих.

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

Примечание. Для простоты в моем примере кода здесь используется новый символ «?».особенность C # 6 (VS 2015).Если вы используете старую версию, просто удалите «?»и явно проверять нулевое значение перед вызовом 'SelectTemplate' выше и возвращать нулевое значение в противном случае, например так:

return inDropDown
    ? DropdownItemsTemplate ??
        ((DropdownItemsTemplateSelector != null)
            ? DropdownItemsTemplateSelector.SelectTemplate(item, container)
            : null)
    : SelectedItemTemplate ??
        ((SelectedItemTemplateSelector != null)
            ? SelectedItemTemplateSelector.SelectTemplate(item, container)
            : null)

Я также включил расширение разметки, которое просто создает и возвращает вышеуказанный класс для удобства в 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
        };
    }
}

А вот как вы это используете.Хорошо, чисто и понятно, а ваши шаблоны остаются «чистыми»

Примечание: «is:» - вот мое отображение xmlns для того, где я поместил класс в код.Убедитесь, что вы импортировали свое собственное пространство имен и изменили «:» соответствующим образом.

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

Или смешатьи матч!Здесь я использую шаблон для выбранного элемента, но селектор шаблона для элементов DropDown.

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

Кроме того, если вы не укажете Template или TemplateSelector для выбранных или раскрывающихся элементов,это просто возвращается к регулярному разрешению шаблонов данных, основанных на типах данных, опять же, как и следовало ожидать.Так, например, в приведенном ниже случае выбранный элемент имеет свой явно заданный шаблон, но раскрывающийся список будет наследовать любой шаблон данных, применяемый для DataType объекта в контексте данных.

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

Наслаждайтесь!

30 голосов
/ 12 января 2011

Простое решение:

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

Если вы хотите отключить весь шаблон, вы также можете сделать это, используя триггер, например, , применив другой ContentTemplate к ContentControl.Это также позволяет сохранить выбор шаблона по умолчанию 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>

Обратите внимание, что этот метод вызовет ошибки привязки, так как относительный источникне найден для выбранного элемента.Альтернативный подход см. MarqueIV's answer .

1 голос
/ 30 апреля 2013

Я использовал следующий подход

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

работал как шарм. Не очень понравилось событие Loaded, но вы можете это исправить, если хотите

1 голос
/ 12 января 2011

Я собирался предложить использовать комбинацию ItemTemplate для комбинированных элементов с параметром Text в качестве выбора заголовка, но я вижу, что ComboBox не учитывает параметр Text.

Я имел дело счто-то похожее, переопределяя ComboBox ControlTemplate.Вот MSDN веб-сайт с примером для .NET 4.0.

В моем решении я изменяю ContentPresenter в шаблоне ComboBox, чтобы он связывался с текстом, а его ContentTemplate связывался с простымDataTemplate, который содержит TextBlock, например:

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

С этой связующей ссылкой я могу управлять отображением выбора Combo напрямую через текстпараметр в элементе управления (который я привязываю к соответствующему значению в моей ViewModel).

0 голосов
/ 06 июля 2018

В дополнение к тому, что сказал H.B. ответ , ошибки связывания можно избежать с помощью конвертера. Следующий пример основан на решении, отредактированном самим 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 # следующий:

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();
    }
}
0 голосов
/ 12 января 2011

Да. Вы используете Выбор шаблона , чтобы определить, какой шаблон связать во время выполнения. Таким образом, если IsSelected = False, тогда используйте этот шаблон, если IsSelected = True, используйте этот другой шаблон.

Примечание: После того, как вы внедрите селектор шаблонов, вам нужно будет дать имена ключей шаблонов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...