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("");
}
}
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.