Domanda

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"?

È stato utile?

Soluzione

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>
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top