Search code examples
wpfxamlimagesource

WPF XAML - Inherit Bursh from Parent to Fascilitate Icon Reuse


I have the following structure in my WPF app:

App.xaml:

<Application.Resources>
    <ResourceDictionary>
        <DrawingImage x:Key="mycion">
            <DrawingImage.Drawing>
                <GeometryDrawing Brush="{WHAT DO I PUT HERE?}" Geometry="M8,8L56,8 56,13.375 8,13.375 8,8z M8,24L8,18.625 56,18.625 56,24 8,24z M8,34.625L8,29.375 56,29.375 56,34.625 8,34.625z M8,45.375L8,40 56,40 56,45.375 8,45.375z M8,56L8,50.625 56,50.625 56,56 8,56z" />
            </DrawingImage.Drawing>
        </DrawingImage>
    </ResourceDictionary>
</Application.Resources>

Window1.xaml:

<Button HorizontalAlignment="Left" Height="49" Margin="33,36,0,0" VerticalAlignment="Top" Width="115">
    <!-- I want this image to be Red. -->
    <Image Height="25" Width="42" Source="{StaticResource mycion}"/>
</Button>

<Button HorizontalAlignment="Left" Height="49" Margin="33,136,0,0" VerticalAlignment="Top" Width="115">
    <!-- I want this image to be Green. -->
    <Image Height="25" Width="42" Source="{StaticResource mycion}"/>
</Button>

Question:

How do I allow for the ability to change Brush color (Red/Green in my example) of the resource (custom icon from a resource dictionary implemented as a DrawingImage) bound to a property that expects ImageSource?

Basically I want to somehow have the ability to have my icons defined in the ResourceDictionaries as Geometries, but with the ability to easily set those as values to properties expecting ImageSource and the ability to change / set the Brush used to render that Geometry on the parent object or in the XAML of the window somehow (so it propagates down to the icon resource).

Note: that I used Image nested in a Button only as an example. The icon need not be set on an image necessarily, but more often as a value to a property on some other element that expects ImageSource.

I am also open to other possibilities - not just using DrawingImage, but I am relatively new to WPF so don't know how else it can be easily achieved, given that:

  • My icons are represented as geometries
  • I do need to be able to set the Brush on a per-UIElement basis (where icon is used)
  • I do not want to set the icon as (part of) the content of the element - it's just too cumbersome and complicated (dealing with layout for a simple button - if you want to have both icon and text, etc.).

Any tips on the matter would be appreciated.


Solution

  • store geometry as a Resource as it is the worst part to repeat (DRY)

    <Geometry x:Key="ico">M8,8L56,8 56,13.375 8,13.375 8,8z M8,24L8,18.625 56,18.625 56,24 8,24z M8,34.625L8,29.375 56,29.375 56,34.625 8,34.625z M8,45.375L8,40 56,40 56,45.375 8,45.375z M8,56L8,50.625 56,50.625 56,56 8,56z</Geometry>
    

    and create a DrawingImage of each color (GeometryDrawing is separated from DrawingImage to make code shorter)

    <GeometryDrawing x:Key="red"  Brush="Red" Geometry="{StaticResource ico}" />
    <DrawingImage x:Key="redIcon" Drawing="{StaticResource red}"/>
    
    <GeometryDrawing x:Key="green"  Brush="Green" Geometry="{StaticResource ico}" />
    <DrawingImage x:Key="greenIcon" Drawing="{StaticResource green}"/>
    

    usage

    <Button HorizontalAlignment="Left" Height="49"  VerticalAlignment="Top" Width="115">
        <Image Height="25" Width="42" Source="{StaticResource redIcon}"/>
    </Button>
    
    <Button HorizontalAlignment="Left" Height="49" VerticalAlignment="Top" Width="115">
        <Image Height="25" Width="42" Source="{StaticResource greenIcon}"/>
    </Button>
    

    if a lot of colors are required, it is better to create a converter which will use geometry resource and return DrawingImage of requested color


    example of converter which I'm talking about:

    public class GeometryColorConverter: IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // getting geometry from App resources
            var g = (Geometry)value;
    
            // set Black as default color 
            string colorName = (string)parameter ?? "Black";
    
            // parsing color name
            var color = (Color)ColorConverter.ConvertFromString(colorName);
    
            // creating Image
            return new DrawingImage
            {
                Drawing = new GeometryDrawing(new SolidColorBrush(color), null, g)
            };            
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    and usage:

    <local:GeometryColorConverter x:Key="paint"/>
    
    <Button HorizontalAlignment="Left" Height="49" VerticalAlignment="Top" Width="115">
        <Image Height="25" Width="42" 
            Source="{Binding Source={StaticResource ico}, 
                             Converter={StaticResource paint}, 
                             ConverterParameter=Orange}"/>
    </Button>