Как ответить
я есть 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;
// [...]
}
}
}