Как ответить

я есть ListBox, который представляет список объектов с привязкой к данным через его ItemSource. Поскольку у каждого объекта есть особые потребности отображения, я определяю ItemTemplateSelector, который возвращает соответствующий DataTemplate в зависимости от объекта. Это все работает безотказно.

Шаблоны данных для каждого объекта следуют общей формуле, но в середине содержат пользовательские элементы. Например:

    <DataTemplate x:Key="collectibleTemplate">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Border BorderBrush="LightGray" BorderThickness="1">
                <Expander IsExpanded="True" Header="{Binding ComponentName}" Background="WhiteSmoke">
                    <StackPanel>
                        <TextBlock Margin="5,5,5,0" Text="{Binding EditDescription}" TextWrapping="Wrap" />

                        <!-- This is the only custom part of each template -->
                        <StackPanel Margin="0,10,5,0" Orientation="Horizontal">
                            <Label Content="Type:" />
                            <ComboBox Height="22" HorizontalAlignment="Left" SelectedItem="{Binding Path=CollectibleType, Mode=TwoWay}"
                                            ItemsSource="{Binding Source={StaticResource collectibleTypeFromEnum}}" />
                        </StackPanel>
                        <!-- End custom part -->

                        <StackPanel Margin="0,0,0,5">
                            <Label Content="Available Actions:" >
                                <Label.Style>
                                    <Style TargetType="Label">
                                        <Setter Property="Visibility" Value="Visible" />
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding EditActions.Count}" Value="0">
                                                <Setter Property="Visibility" Value="Collapsed" />
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </Label.Style>
                            </Label>
                            <ItemsControl ItemsSource="{Binding EditActions}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <Button Command="{Binding}" Content="{Binding Title}" ToolTip="{Binding ToolTip}" Margin="5,0,5,0"/>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </StackPanel>
                    </StackPanel>
                </Expander>
            </Border>
        </Grid>
    </DataTemplate>

Как вы можете видеть, существует много общего XAML, заключающего небольшой пользовательский раздел в середину.

Дополнительные шаблоны данных будут написаны другими инженерами (они захотят создать один для каждого нового типа объекта, который они добавляют), поэтому я заинтересован в том, чтобы сделать новый DataTemplate максимально надежным и безболезненным. Конечно, не нужно копировать весь DataTemplate с добавленными в середине пользовательскими «вещами» - но я также не пристрастен к тому, чтобы извлекать части шаблона как части многократного использования и ссылаться на них, потому что это все еще приводит к большому количеству дублирующегося кода в каждый новый DataTemplate, а это означает возможные ошибки и жесткую ремонтопригодность. То есть, этот подход является более приемлемым, но все еще кажется неоптимальным:

<DataTemplate x:Key="collectibleTemplate">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Border BorderBrush="LightGray" BorderThickness="1">
                <Expander IsExpanded="True" Header="{Binding ComponentName}" Background="WhiteSmoke">
                    <StackPanel>
                        <TextBlock Margin="5,5,5,0" Text="{Binding EditDescription}" TextWrapping="Wrap" />

                        <!-- This is the only custom part of each template -->
                        [...]
                        <!-- End custom part -->

                        <ContentPresenter Content="{StaticResource AvailableActions}" />

                    </StackPanel>
                </Expander>
            </Border>
        </Grid>
    </DataTemplate>

    <StackPanel Margin="0,0,0,5" x:Key="AvailableActions" x:Shared="false">
        <Label Content="Available Actions:" >
            <Label.Style>
        <!-- 
        [Bottom half of shared XAML from the first example, offloaded here]
        -->
    </StackPanel>

Итак: какова моя лучшая стратегия для решения этой проблемы? AFAIK Я застрял с использованием DataTemplates, потому что это единственный элемент, который принимает ListBox ItemTemplateSelector. Есть ли способ создать составной DataTemplate в DataTemplateSelector? Я бы предоставил стандартный DataTemplate, который используется всеми объектами, и ссылки на DataTemplateSelector в бите пользовательского XAML, необходимого для каждого типа объекта. Другие инженеры подключились бы к такому обобщенному поведению кода.

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

И, просто для справки: мой текущий DataTemplateSelector очень прост. Именно здесь я и ожидал бы создать окончательный шаблон DataTemplate, а не просто возвращать тот, который жестко задан в XAML.

public class NodeComponentDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        FrameworkElement element = container as FrameworkElement;

        if (element != null && item != null)
        {
            if (item is CollectibleComponent)
                return element.FindResource("collectibleTemplate") as DataTemplate;

            // [...]
        }
    }
}