0

General: in a Style for a custom control, is it possible to bind to another DependencyProperty (e.g. MySecondProperty) value from within a <Setter Property="MyFirstProperty">?

For what purpose? To accomplish the following:

1.) Derive some MyButton : Button control, which has an additional List<string> FlyoutSource dependency property on it.

2.) Define a MyButtonStyle, which has a <Setter Property="Flyout"> element defining the Button.Flyout property (since MyButton : Button).

The Flyout will have a ListView in it, whose ItemsSource must bind to MyButton.FlyoutSource

<Style TargetType="local:MyButton" x:Key="MyButtonStyle">
    <Setter Property="Background" Value="Green"/>
    <Setter Property="Flyout">
        <Setter.Value>
            <Flyout>
                <!-- &&&&&&& THE FOLLOWING LINE DOES NOT WORK PROPERLY &&&&&&& -->
                <ListView ItemsSource="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=FlyoutSource}">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding}"/>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </Flyout>
        </Setter.Value>
    </Setter>
</Style>

How would I like to use the solution:

<local:MyButton 
    FlyoutSource="{x:Bind FlyoutSourceList, Mode=TwoWay}"
    Style="{StaticResource MyButtonStyle}">
</local:MyButton

More Detail: the MyButton class:

public class MyButton : Button
{
    public MyButton()
    {
        this.DefaultStyleKey = typeof(Button);
    }

    public static DependencyProperty FlyoutSourceProperty = DependencyProperty.Register(
    "FlyoutSource", typeof(List<string>), typeof(MyButton),
    new PropertyMetadata(null, new PropertyChangedCallback(OnFlyoutSourceChanged)));

    public List<string> FlyoutSource
    {
        get { return (List<string>)GetValue(FlyoutSourceProperty); }
        set { SetValue(FlyoutSourceProperty, value); }
    }

    public static void OnFlyoutSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        Debug.WriteLine("");
    }
}
bunkerdive
  • 2,031
  • 1
  • 25
  • 28
  • I have no idea what you're asking here dude. Are you just asking how to make a standard flyout template apply to every or specific button instance. Kind of like ....``? – Chris W. Nov 04 '16 at 17:50
  • Updating the question. Ultimately, I'm trying to create a control derived from `Button`, but which creates a flyout containing a `ListView` whose `ItemsSource` is bound to a DP on my custom button. – bunkerdive Nov 04 '16 at 18:11

1 Answers1

1

You actually don't need to subclass Button to do this, just make FlyoutSource an attached property.

You can't use RelativeSource mode TemplatedParent here because it is not within a ControlTemplate.

It seems that the only way for the flyout content to obtain information from it's attached element is through DataContext inheritance. I could only come up with this, but it involves a lot of binding gymnastics. I don't recommend it.

public class ViewProps
{
    public static object GetFlyoutListSource(DependencyObject obj)
    {
        return (object)obj.GetValue(FlyoutListSourceProperty);
    }

    public static void SetFlyoutListSource(DependencyObject obj, object value)
    {
        obj.SetValue(FlyoutListSourceProperty, value);
    }

    public static readonly DependencyProperty FlyoutListSourceProperty =
        DependencyProperty.RegisterAttached("FlyoutListSource", typeof(object), typeof(ViewProps), new PropertyMetadata(null));
}
<Grid x:Name="MyGrid">
    <Grid.Resources>
        <Style x:Key="FlyoutButton" TargetType="Button">
            <Setter Property="Flyout">
                <Setter.Value>
                    <Flyout>
                        <ListView ItemsSource="{Binding (local:ViewProps.FlyoutListSource)}"/>
                    </Flyout>
                </Setter.Value>
            </Setter>
        </Style>
    </Grid.Resources>

    <Button
        Style="{StaticResource FlyoutButton}"
        Content="Button"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        local:ViewProps.FlyoutListSource="{Binding ElementName=MyGrid, Path=DataContext.ItemsSource}"/>
</Grid>

If you want to subclass Button, then you can do something like this.

ListFlyoutButton.cs

public sealed class ListFlyoutButton : Button
{
    public object ItemsSource
    {
        get { return (object)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource", typeof(object), typeof(ListFlyoutButton), new PropertyMetadata(null));

    public ListFlyoutButton()
    {
        this.DefaultStyleKey = typeof(ListFlyoutButton);
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        ((FrameworkElement)((Flyout)Flyout).Content).DataContext = this;
    }
}

Themes\Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="***">

    <Style TargetType="local:ListFlyoutButton">
        <Setter Property="Background" Value="{ThemeResource ButtonBackground}" />
        <Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
        <Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}" />
        <Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
        <Setter Property="Padding" Value="8,4,8,4" />
        <Setter Property="HorizontalAlignment" Value="Left" />
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
        <Setter Property="FontWeight" Value="Normal" />
        <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
        <Setter Property="UseSystemFocusVisuals" Value="True" />
        <Setter Property="FocusVisualMargin" Value="-3" />
        <Setter Property="Flyout">
            <Setter.Value>
                <Flyout>
                    <ListView ItemsSource="{Binding ItemsSource}"/>
                </Flyout>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:ListFlyoutButton">
                    <Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal">
                                    <Storyboard>
                                        <PointerUpThemeAnimation Storyboard.TargetName="RootGrid" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="PointerOver">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPointerOver}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBorderBrushPointerOver}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonForegroundPointerOver}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <PointerUpThemeAnimation Storyboard.TargetName="RootGrid" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPressed}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBorderBrushPressed}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonForegroundPressed}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <PointerDownThemeAnimation Storyboard.TargetName="RootGrid" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundDisabled}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBorderBrushDisabled}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonForegroundDisabled}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <ContentPresenter x:Name="ContentPresenter"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Content="{TemplateBinding Content}"
                                ContentTransitions="{TemplateBinding ContentTransitions}"
                                ContentTemplate="{TemplateBinding ContentTemplate}"
                                Padding="{TemplateBinding Padding}"
                                HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                                VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                                AutomationProperties.AccessibilityView="Raw" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

MainPage.xaml

<local:ListFlyoutButton Content="Button" ItemsSource="{Binding Items}"/>

MainPage.xaml.cs

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        DataContext = new
        {
            Items = new[] { "Apple", "Banana" },
        };
    }
}

It would be nice if we didn't have to duplicate the entire default Button style.

Decade Moon
  • 32,968
  • 8
  • 81
  • 101