1
votes

I'm creating a custom control and I'm trying to create partially specified template for list box items. The template has some predefined parts and there should be another part that can be templated when using the control.

For this I have created a dependency property named SuggestionItemTemplate like so:

public static readonly DependencyProperty SuggestionItemTemplateProperty =
    DependencyProperty.Register("SuggestionItemTemplate", 
        typeof(DataTemplate), 
        typeof(AutoSuggestTextBox), 
        new PropertyMetadata(null));

In my custom controls' generic.xaml I have:

<Style TargetType="local:AutoSuggestTextBox">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:AutoSuggestTextBox">
                <Grid>
                    <ListBox x:Name="ItemsControl">
                        <ListBox.ItemsPanel>
                            <ItemsPanelTemplate>
                                <StackPanel />
                            </ItemsPanelTemplate>
                        </ListBox.ItemsPanel>
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*" />
                                        <ColumnDefinition Width="Auto" />
                                    </Grid.ColumnDefinitions>
                                    <ContentPresenter Grid.Column="0" 
                                                      ContentTemplate="{TemplateBinding SuggestionItemTemplate}" 
                                                      Content="{Binding}" />
                                    <ToggleButton Grid.Column="1" 
                                                  x:Name="DetailsHover" 
                                                  ClickMode="Hover" 
                                                  Style="{StaticResource DetailsToggleButtonStyle}" />
                                </Grid>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Unfortunatelly, this does not work as it's not possible to use TemplateBinding from inside ContentPresenter nested into DataTemplate. (The member "SuggestionItemTemplate" is not recognized or is not accessible.)

I also tried to use ancestor binding (available in Silverlight 5) like:

<ContentPresenter Grid.Column="0" 
                  ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:AutoSuggestTextBox}, Path=SuggestionItemTemplate}" 
                  Content="{Binding}" />

But this results in binding error:

Error: System.Exception: BindingExpression_CannotFindAncestor

I suppose this happens because I'm inside ControlTemplate of my custom control and "local:AutoSuggestTextBox" is not defined anywhere in the style.

The third option that I tried was to apply ContentTemplate in OnApplyTemplate override but this also doesn't work:

var cp = itemsControlElement.ItemTemplate.LoadContent() as ContentPresenter;
cp.ContentTemplate = SuggestionItemTemplate;

In all cases, I get my grid with two columns, toggle button is visible but content presenter simple prints out view model's type name. (I believe this is the default behavior if the ContentTemplate is null).

Is this even possible to do? Are there any other ways to specify a partial template and then only add customized template part when necessary?

As a workaround for now, I can specify

ItemTemplate="{TemplateBinding SuggestionItemTemplate}"

for the list box and then copy/paste the generic template everywhere I use this control. But this is the behavior I'm hoping to avoid in the first place.

Thanks!

edit: I used the code tags for all blocks of code, but they're not highlighted for some reason. :/

2

2 Answers

0
votes

It is possible to walk through Visual Ancestors in the OnApplyTemplate method, find your ContentPresenter(s) and set the ItemTemplate on that. To my mind, this is fine for a single item, but not so much in an ItemsControl scenario.

You could achieve what you are after using your own custom Control. Just give it a Content dependency property of type Object, and a Template DP of type DataTemplate (and multiples of the two if you fancy), and you can set up the root visual style and templates in the default style for your Control.

In this specific case, I would suggest that the best approach is to put your ToggleButton in the ListBoxItem template instead by customising the ListBox.ItemContainerStyle. It is easy to modify the default Control Template using Expression Blend, and the DataContext of the ToggleButton will not change, so the changes to your own logic should be minimal.

Edit: If you mean to use a number of different data templates, perhaps Implicit Data Templates will be more suitable.

0
votes

I managed to solve this using a different approach. I used ancestor binding but instead of trying to reach the root control (my AutoSuggestTextBox) from the DataTemplate, I ask for a reference to my ListBox (here named ItemsControl).

However, since the ListBox doesn't have the SuggestionItemTemplate property, I sub-classed it to my own CustomListBox where I implemented that property. It all comes down to this code snippet:

<Style TargetType="local:AutoSuggestTextBox">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="local:AutoSuggestTextBox">
        <Grid>
          <local:CustomizableListBox x:Name="ItemsControl"
                                     SuggestionItemTemplate="{TemplateBinding SuggestionItemTemplate}">
            <local:CustomizableListBox.ItemTemplate>
              <DataTemplate>
                <Grid>
                  <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                  </Grid.ColumnDefinitions>
                  <ContentPresenter Grid.Column="0" 
                                    ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:CustomizableListBox}, Path=SuggestionItemTemplate}"
                                    Content="{Binding}" />
                  <ToggleButton Grid.Column="1" 
                                x:Name="DetailsHover" 
                                ClickMode="Hover" 
                                Style="{StaticResource DetailsToggleButtonStyle}" />
                 </Grid>
              </DataTemplate>
            </local:CustomizableListBox.ItemTemplate>
          </local:CustomizableListBox>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>