Добавить различные элементы управления в ItemsControl

Мне нужно добавить различные элементы управления (TextBox / CheckBox / ComboBox и т. Д.) В ItemsControl в зависимости от определенного условия. Каждый элемент в ItemsControl представляет собой пару «имя-значение». Имя всегда представлено TextBlock, но Value может быть любым элементом управления пользовательского интерфейса. Я использую StackPanel с горизонтальным выравниванием для представления каждого элемента. Первый элемент управления в StackPanel остается TextBlock, но второй элемент управления зависит от свойства «ItemDataType», установленного в ViewModel во время выполнения.

У меня проблема в том, что я не могу назначать различные элементы управления во втором элементе StackPanel, используя триггер стиля со свойством ItemDataType.

Фрагмент кода:

<UserControl.Resources>

    <DataTemplate x:Key="TextBoxTemplate">
        <TextBox Text="{Binding Path=DataValue}"/>
    </DataTemplate>

    <DataTemplate x:Key="ComboBoxTemplate">
        <ComboBox ItemsSource="{Binding Path=SelectionList}" SelectedValue="{Binding Path=DataValue,Mode=TwoWay}"/>
    </DataTemplate>

    <DataTemplate x:Key="CheckBoxTemplate">
        <CheckBox IsChecked="{Binding Path=DataValue,Mode=TwoWay}" />
    </DataTemplate>

    <DataTemplate x:Key="ButtonTemplate">
        <Button Content="{Binding Path=DataValue}"/>
    </DataTemplate>

    <DataTemplate x:Key="dynamicTemplate">
        <StackPanel Orientation="Horizontal" Tag="{Binding ItemDataType}">
            <TextBlock Text="{Binding Path=DataName,Mode=TwoWay}"/>
            <ContentControl>
                <ContentControl.Style>
                    <Style TargetType="{x:Type ContentControl}">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding ItemDataType}" Value="TextBox">
                                <Setter Property="Template" Value="{StaticResource TextBoxTemplate}"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </ContentControl.Style>
            </ContentControl>
        </StackPanel>
    </DataTemplate>

</UserControl.Resources>

<Grid>
    <!-- CONTROL LAYOUT -->
    <ItemsControl ItemsSource="{Binding Path=DataList,Mode=TwoWay}" ItemTemplate="{StaticResource dynamicTemplate}">

        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel></StackPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</Grid>

Error I get is DataTemplate invalid for ContentControl.Template property. I understand that what I am doing is wrong, but I want help to do it right way.

Спасибо,

RDV


person RDV    schedule 13.01.2016    source источник


Ответы (2)


Ответ (или ответ) - написать DataTemplateSelector, который возвращает правильный шаблон на основе некоторого произвольного параметра. Что-то вроде следующего (извините за шаблонный шум с проверкой нуля; он копируется прямо из производственного кода).

/// <summary>
/// Selects template based on the value of a named property of the data item. 
/// Property name is specified by ResourceKeyPropertyName.
/// </summary>
public class PropertyValueTemplateSelector : DataTemplateSelector
{
    /// <summary>
    /// Gets or sets a path to a value on the source object to serve as a resource key for 
    /// the DataTemplate used to display the source object.
    /// </summary>
    public string ResourceKeyPropertyName { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var context = container as FrameworkElement;
        DataTemplate template = null;

        if (null == container)
        {
            throw new NullReferenceException("container");
        }
        else if (null == context)
        {
            throw new Exception("container must be FramekworkElement");
        }
        else if (String.IsNullOrEmpty(ResourceKeyPropertyName))
        {
            throw new NullReferenceException("ResourceKeyPropertyName");
        }
        else if (null == item)
        {
            return null;
        }

        var prop = item.GetType().GetProperty(ResourceKeyPropertyName);

        if (null == prop)
        {
            throw new Exception("Undefined property " + ResourceKeyPropertyName);
        }

        var resourceKey = prop.GetValue(item, null);

        if (null != resourceKey)
        {
            try
            {
                template = context.FindResource(resourceKey) as DataTemplate;
            }
            catch (Exception ex)
            {
                Ability.CAPS.WPF.Utilities.ErrorHandler.HandleException(ex, Ability.Logging.AbilityExceptionPolicy.GeneralExceptionPolicy);
                template = null;
            }
        }

        return template ?? base.SelectTemplate(item, container);
    }
}

Используйте в XAML так:

<ItemsControl
    >
    <ItemsControl.ItemTemplateSelector>
        <!-- Tell it use the value of the "DataName" property as the 
             resource key for the template it uses.
        -->
        <local:PropertyValueTemplateSelector 
            ResourceKeyPropertyName="DataName" />
    </ItemsControl.ItemTemplateSelector>
    <!-- etc. 
         etc. 
         etc. -->
</ItemsControl>

Если свойство DataName элемента - «Foo», это будет искать в локальном контексте DataTemplate, ресурсный ключ которого «Foo», и использовать его - так что назовите свой DataTemplates соответствующим образом и отправляйтесь в город. Нет более простого или обобщенного способа сделать это. Между прочим, в нашем коде это используется только в одном месте на данный момент (я написал это только в прошлом месяце), и он использует значения перечисления для ключей ресурсов, а не строки. Подойдет любой объект. Ключи ресурсов не обязательно должны быть строками.

В качестве альтернативы вы можете захотеть выполнить поиск или что-то еще, чтобы преобразовать значения DataName в другой набор ключей. Ваш звонок, если это больше соответствует вашим потребностям.

Другой вариант - иметь один шаблон элемента с набором триггеров, который устанавливает шаблон для внутреннего ContentControl в зависимости от значения DataName. Вот ответ, который делает что-то подобное.

person 15ee8f99-57ff-4f92-890c-b56153    schedule 13.01.2016

Я хотел иметь решение XAML - мне потребовалось время :-). Ниже рабочий код:

    <Style x:Key="nvpTextBlockStyle" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
        <Setter Property="HorizontalAlignment" Value="Left"/>
        <Setter Property="Width" Value="{Binding Path=LabelWidthStr, FallbackValue=50}"/>
        <Setter Property="Margin" Value="0,5,0,5"/>
        <Setter Property="Text" Value="{Binding Path=NameData,Mode=TwoWay}"/>
        <Setter Property="FontSize" Value="16"/>
    </Style>

    <DataTemplate x:Key="textBoxTemplate">
        <TextBox Margin="1,1" Text="{Binding Path=ValueData,UpdateSourceTrigger=PropertyChanged,
                    ValidatesOnExceptions=True,NotifyOnValidationError=True,ValidatesOnDataErrors=True}"/>
    </DataTemplate>

    <DataTemplate x:Key="comboBoxTemplate">
        <ComboBox HorizontalAlignment="Left" ItemsSource="{Binding Path=SelectionList}" 
                      SelectedValue="{Binding Path=ValueData,Mode=TwoWay}"
                      IsEnabled="{Binding IsDataItemEnabled}"/>
    </DataTemplate>

    <DataTemplate x:Key="checkBoxTemplate">
        <CheckBox HorizontalAlignment="Left" VerticalAlignment="Center"  
                      IsChecked="{Binding Path=ValueData,Mode=TwoWay}"/>
    </DataTemplate>

    <DataTemplate x:Key="buttonTemplate">
        <Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.AddCommand}" 
                    CommandParameter="{Binding}" Width="30" Height="25">
            <TextBlock Text="&#x1F511;" FontFamily="Segoe UI Symbol" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" />
        </Button>
    </DataTemplate>

    <DataTemplate x:Key="dynamicTemplate">
        <StackPanel Orientation="Horizontal" Margin ="5">
            <TextBlock Style="{StaticResource nvpTextBlockStyle}"/>
            <ContentPresenter Content="{Binding}" 
                Tag="{Binding Path=CustomDataType, FallbackValue={x:Static local:CustomViewModel.TEXTBOX_TEMPLATE}}">
                <ContentPresenter.Resources>
                    <Style TargetType="{x:Type ContentPresenter}">
                        <Style.Triggers>
                            <Trigger Property="Tag" Value="{x:Static local:CustomViewModel.TEXTBOX_TEMPLATE}">
                                <Setter Property="ContentTemplate" Value="{StaticResource textBoxTemplate}"/>
                            </Trigger>
                            <Trigger Property="Tag" Value="{x:Static local:CustomViewModel.COMBOBOX_TEMPLATE}">
                                <Setter Property="ContentTemplate" Value="{StaticResource comboBoxTemplate}"/>
                            </Trigger>
                            <Trigger Property="Tag" Value="{x:Static local:CustomViewModel.CHECKBOX_TEMPLATE}">
                                <Setter Property="ContentTemplate" Value="{StaticResource checkBoxTemplate}"/>
                            </Trigger>
                            <Trigger Property="Tag" Value="{x:Static local:CustomViewModel.BUTTON_TEMPLATE}">
                                <Setter Property="ContentTemplate" Value="{StaticResource buttonTemplate}"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </ContentPresenter.Resources>
            </ContentPresenter>
        </StackPanel>
    </DataTemplate>

    <Grid>       
    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <ItemsControl  ItemsSource="{Binding Path=CustomDataList,Mode=TwoWay}" 
                       ItemTemplate="{StaticResource dynamicTemplate}";
                       KeyboardNavigation.IsTabStop="False">
            <ItemsPanelTemplate>
                <StackPanel></StackPanel>
            </ItemsPanelTemplate>
        </ItemsControl>
    </ScrollViewer>

</Grid>

Спасибо,

RDV

person RDV    schedule 04.04.2018