Question

Let's say I'm writing an ItemsControl control template, and need to refer to the ItemsPanel instance for some reason. Since not in the same namescope, there's not really a way to bind to it.

I'm using a custom items panel -- a carousel-type panel that slides from one item to the next -- and would like a button in the control template to maneuver left or right:

<Style TargetType="custom:AnItemsControl">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <custom:SlideContentPanel />
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="custom:AnItemsControl">
                <Grid>
                    <ItemsPresenter />
                    <Button Content="Next" HorizontalAlignment="Right" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

How can the Button in this example refer to methods like "GoRight()" and properties "CanGoRight" on the items panel "SlideContentPanel"?

Was it helpful?

Solution

You can't refer to the items panel instance directly. But you can define a dependency property on the ItemsControl, and set it in code-behind when the control loads. Then the control template can refer to that property via a TemplateBinding.

So in the example, "AnItemsControl" should define a dependency property of type "SlideContentPanel":

    public SlideControl()
    {
        DefaultStyleKey = typeof(SlideControl);
        Loaded += SlideControl_Loaded;
    }

    public SlideContentPanel TypedPanel
    {
        get { return (SlideContentPanel)GetValue(TypedPanelProperty); }
        set { SetValue(TypedPanelProperty, value); }
    }
    public static readonly DependencyProperty SlidePanelProperty =
        DependencyProperty.Register("TypedPanel", typeof(SlideContentPanel), typeof(AnItemsControl), new PropertyMetadata(null));

Now, when the control loads, locate the panel using VisualTreeHelper, and set the dependency property:

    public AnItemsControl()
    {
        DefaultStyleKey = typeof(AnItemsControl);
        Loaded += AnItemsControl_Loaded;
    }

    private void AnItemsControl_Loaded(object sender, RoutedEventArgs e)
    {
        TypedPanel = FindItemsPanel<SlideContentPanel>(this);
    }

    private T FindItemsPanel<T>(FrameworkElement visual)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
        {
            FrameworkElement child = VisualTreeHelper.GetChild(visual, i) as FrameworkElement;
            if (child != null)
            {
                if (child is T && VisualTreeHelper.GetParent(child) is ItemsPresenter)
                {
                    object temp = child;
                    return (T)temp;
                }

                T panel = FindItemsPanel<T>(child);
                if (panel != null)
                {
                    object temp = panel;
                    return (T)temp;
                }
            }
        }
        return default(T);
    }

And now the button in "AnItemsControl"'s control template can refer to the panel by using the "TypedPanel" property in the templated parent:

<Button Content="Next" HorizontalAlignment="Right" 
        Visibility="{Binding RelativeSource={RelativeSource TemplatedParent},
                             Path=TypedPanel.CanGoRight,
                             Converter={StaticResource BoolToVisibilityConverter}}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Tap">
            <ei:CallMethodAction 
                TargetObject="{Binding RelativeSource={RelativeSource TemplatedParent},Path=TypedPanel}" 
                MethodName="GoRight" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top