Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding to a nested property inside ControlTemplate

Tags:

xaml

The aim is to build up a resource dictionary of icon path geometries for my custom button template.

What works so far:

ResourceDictionary:

<Geometry x:Key="ArrowDown">
    M0,10 M10,0 M5,1 L5,7 4.2,7 5,8 5.8,7 5,7
</Geometry>
<Geometry x:Key="ArrowUp">
    M0,0 M10,10 M5,9 L5,3 4.2,3 5,2 5.8,3 5,3
</Geometry>

Attached property for the Path:

public static Geometry GetIconPath(UIElement element)
{
    return (Geometry)element.GetValue(IconPathProperty);
}

public static void SetIconPath(UIElement element, Geometry value)
{
    element.SetValue(PIconPathProperty, value);
}

public static readonly DependencyProperty IconPathProperty =
    DependencyProperty.RegisterAttached("IconPath", typeof(Geometry), typeof(Attached));

Template:

<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
    <Viewbox>
        <Path Name="Icon" Stroke="Black" StrokeThickness="1" Stretch="Uniform"
              Data="{Binding (local:Attached.IconPath), RelativeSource={RelativeSource TemplatedParent}}"/>
    </Viewbox>
    <ControlTemplate.Triggers>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="Stroke" TargetName="Icon" Value="Gray"/>
            <Setter TargetName="Icon" Property="Effect">
                <Setter.Value>
                    <DropShadowEffect ShadowDepth="0.2" Color="Black" Direction="125"/>
                </Setter.Value>
            </Setter>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

The button:

<Button local:Attached.IconPath="{StaticResource ArrowDown}"/>

This works fine, but I now have an added layer of complexity: some of the icons need to be Filled and others don't. And I'd like to have this information in the ResourceDictionary with the GeometryData.

One solution would be to store the whole Path in the ResourceDictionary, but as you can see in the ControlTemplate, I need to be able to access the Path to trigger the DropShadowEffect.

So the alternative solution I'm attempting is to use a POCO to store both elements in:

public class IconData
{
    public bool Fill { get; set; }
    public Geometry Geometry { get; set; }
}

So the ResourceDictionary now contains:

<local:IconData x:Key="ArrowUp" Fill="True" Geometry="
    M0,0 M10,10 M5,9 L5,3 4.2,3 5,2 5.8,3 5,3"/>

With the appropriate attached Property, the ControlTemplate becomes:

<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
    <Viewbox>
        <Path Name="Icon" Stroke="Black" StrokeThickness="1" Stretch="Uniform"
              Data="{Binding (local:Attached.IconData.Geometry), RelativeSource={RelativeSource TemplatedParent}}"/>
    </Viewbox>
    <ControlTemplate.Triggers>
        <Trigger Property="local:Attached.IconData.Fill" Value="True">
            <Setter Property="Fill" TargetName="Icon" Value="#00000000"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="Stroke" TargetName="Icon" Value="Gray"/>
            <Setter TargetName="Icon" Property="Effect">
                <Setter.Value>
                    <DropShadowEffect ShadowDepth="0.2" Color="Black" Direction="125"/>
                </Setter.Value>
            </Setter>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

And unfortunately, both references to IconData here say Nested types are not supported: Attached.IconData.

I'm open to a solution to my implementation, or any workaround or different solution.

like image 237
Benjol Avatar asked Mar 16 '18 14:03

Benjol


1 Answers

<Path Name="Icon" Stroke="Black" StrokeThickness="1" Stretch="Uniform"
      Data="{Binding (local:Attached.IconData.Geometry), RelativeSource={RelativeSource TemplatedParent}}"/>

Here (local:Attached.IconData.Geometry) means a nested type. To get a property of Attached.IconData object one should use following binding path:

<Path Name="Icon" Stroke="Black" StrokeThickness="1" Stretch="Uniform"
      Data="{Binding (local:Attached.IconData).Geometry, RelativeSource={RelativeSource TemplatedParent}}"/>

Also be sure to change the attached property type from Geometry to IconData in Attached type.

IconData class shouldn't be nested to any type, because it's exactly what is said to be not supported (just put in in the same namespace as other used types).

After these modifications your code started working for me.

Here's main window XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="250" Width="260">
    <Window.Resources>
        <ResourceDictionary>
            <local:IconData x:Key="ArrowUp" Fill="False" Geometry="M0,0 M10,10 M4.8,9 4.8,3 4.2,3 5,2 5.8,3 5.2,3 5.2,9 Z"/>
            <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
                <Viewbox>
                    <Path Name="Icon" Stretch="Uniform"
                          Data="{Binding (local:Attached.IconPath).Geometry, RelativeSource={RelativeSource TemplatedParent}}">
                        <Path.Style>
                            <Style TargetType="Path">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding (local:Attached.IconPath).Fill, RelativeSource={RelativeSource TemplatedParent}}"
                                                     Value="True">
                                        <Setter Property="Fill" Value="Black" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding (local:Attached.IconPath).Fill, RelativeSource={RelativeSource TemplatedParent}}"
                                                     Value="False">
                                        <Setter Property="Stroke" Value="Black" />
                                        <Setter Property="StrokeThickness" Value="0.2" />
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Path.Style>
                    </Path>
                </Viewbox>
            </ControlTemplate>

        </ResourceDictionary>
    </Window.Resources>

    <Button local:Attached.IconPath="{StaticResource ArrowUp}" Template="{StaticResource ButtonTemplate}" />
</Window>

Note, that I've amended the path geometry on M0,0 M10,10 M4.8,9 4.8,3 4.2,3 5,2 5.8,3 5.2,3 5.2,9 Z to make it closed and thus suitable to fill.

Here are screenshots for cases <local:IconData Fill="False" ... /> and <local:IconData Fill="True" ... />:

A screenshot for case Fill=False A screenshot for case Fill=True

like image 59
stop-cran Avatar answered Sep 30 '22 16:09

stop-cran