wpfwpf-controlsresourcedictionarywpf-4.0visualbrush

Using a Canvas as an icon with bindable color


As many WPFers, I use this pattern to create a vector icon:

1 - Defining a Canvas contains the data:

Resource Item:

<Canvas Width="256" Height="256" ClipToBounds="True" x:Key="SubmitVisualIcon">
    <Path Fill="#FFFFFF00">
        <Path.Data>
            <PathGeometry FillRule="Nonzero" Figures="M44.436129,25.256006L54.222273,25.256006 75.259996,46.29286 70.368799,51.187792 54.094614,67.462006 44.561911,67.462006 44.436129,67.337162 62.016504,49.752106 15.633995,49.752106 15.633995,42.837337 62.016504,42.837337z M45,5.6100006C23.245507,5.6100006 5.6100006,23.245506 5.6100006,45 5.6100006,66.754498 23.245507,84.389999 45,84.389999 66.754499,84.389999 84.389997,66.754498 84.389997,45 84.389997,23.245506 66.754499,5.6100006 45,5.6100006z M45,0C69.852816,0 89.999998,20.147187 89.999998,45 89.999998,69.852814 69.852816,90.000004 45,90.000004 20.147188,90.000004 9.5367432E-07,69.852814 0,45 9.5367432E-07,20.147187 20.147188,0 45,0z"/>
        </Path.Data>
    </Path>
</Canvas>

2 - Using it in a control template:

ControlTemplate:

<ControlTemplate x:Key="MyButton" TargetType="{x:Type Button}">
    <Border x:Name="root">
        <Grid>
            <Rectangle VerticalAlignment="Center" HorizontalAlignment="Center" 
                       RenderOptions.BitmapScalingMode="HighQuality"
                       Width="32" Height="32">
                <Rectangle.Fill>
                    <VisualBrush Stretch="Fill">
                        <VisualBrush.Visual>
                            <Binding Path="(ui:UIElement.VisualIcon)"
                                     RelativeSource="{RelativeSource TemplatedParent}"/>
                        </VisualBrush.Visual>
                    </VisualBrush>
                </Rectangle.Fill>
            </Rectangle>
            <ContentPresenter />
        </Grid>
    </Border>
</ControlTemplate>

which the ui:UIElement.VisualIcon property, is an attached property to telling the template which resource to use. For example:

<Button Content="Save" ui:UIElement.VisualIcon="{DynamicResource SubmitVisualIcon}">

Now, as you can see, I have to get the Path in the Canvas a Fill value:

<Path Fill="#FFFFFF00">

The question is, is it possible to bind the Fill value to something on TemplatedParent? e.g. I have an attached property to holding icon brush:

<Button Content="Save" ui:UIElement.VisualIcon="{DynamicResource SubmitVisualIcon}"
                       ui:UIElement.VisualIconForeground="Some_Brush">

And I tried to use it like below:

<Path Fill="{Binding ui:UIElement.VisualIconForeground,RelativeSource={RelativeSource TemplatedParent}}">

But it doesn't work. Have you any idea to do this? I mean binding a resource item's some property to be set where they will be used? -Bad English, I know, sorry :(


Solution

  • Finally I found a solution that works in case. I should use Rectangle.OpacityMask instead of Rectangle.Fill:

    The resource:

    <Canvas Width="256" Height="256" ClipToBounds="True" x:Key="SubmitVisualIcon">
        <Path Fill="#FFFFFF00">
            <Path.Data>
                <PathGeometry FillRule="Nonzero" Figures="M44.436129,25.256006L54.222273,25.256006 75.259996,46.29286 70.368799,51.187792 54.094614,67.462006 44.561911,67.462006 44.436129,67.337162 62.016504,49.752106 15.633995,49.752106 15.633995,42.837337 62.016504,42.837337z M45,5.6100006C23.245507,5.6100006 5.6100006,23.245506 5.6100006,45 5.6100006,66.754498 23.245507,84.389999 45,84.389999 66.754499,84.389999 84.389997,66.754498 84.389997,45 84.389997,23.245506 66.754499,5.6100006 45,5.6100006z M45,0C69.852816,0 89.999998,20.147187 89.999998,45 89.999998,69.852814 69.852816,90.000004 45,90.000004 20.147188,90.000004 9.5367432E-07,69.852814 0,45 9.5367432E-07,20.147187 20.147188,0 45,0z"/>
            </Path.Data>
        </Path>
    </Canvas>
    

    The Template:

    <ControlTemplate x:Key="MyButton" TargetType="{x:Type Button}">
        <Border x:Name="root">
            <Grid>
                <Rectangle VerticalAlignment="Center" HorizontalAlignment="Center" 
                           RenderOptions.BitmapScalingMode="HighQuality"
                           Width="32" Height="32"
                           Fill="{TemplateBinding ui:UIElement.VisualIconForeground}">
                    <!-- fill the rectangle with what color do you want, it also can be bounded to every thing -->
                    <!-- and then, use the Canvas as a OpacityMask on rectangle, just like this: -->
                    <Rectangle.OpacityMask>
                        <VisualBrush Stretch="Fill">
                            <VisualBrush.Visual>
                                <Binding Path="(ui:UIElement.VisualIcon)"
                                         RelativeSource="{RelativeSource TemplatedParent}"/>
                            </VisualBrush.Visual>
                        </VisualBrush>
                    </Rectangle.OpacityMask>
                    <!-- this will show the icon with color you defined in Rectangle.Fill -->
                </Rectangle>
                <ContentPresenter />
            </Grid>
        </Border>
    </ControlTemplate>
    

    Usage:

    <Button Content="Save" ui:UIElement.VisualIcon="{DynamicResource SubmitVisualIcon}"
                           ui:UIElement.VisualIconForeground="Some_Brush">
    

    Another Usage:

    <Button Content="Save" ui:UIElement.VisualIcon="{DynamicResource SubmitVisualIcon}"
                           ui:UIElement.VisualIconForeground="Some_Another_Brush">
    

    Another Usage 2:

    <Button Content="Save" ui:UIElement.VisualIcon="{DynamicResource AnotherVisualIcon}"
                           ui:UIElement.VisualIconForeground="Some_Another_Brush">