Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF Bind a Geometry based Image to MenuItem.Icon property

I'm sure that somebody else found a way to do this, I am close but not completely. I successfully use Path objects with Geometry bound to resources based on a resource name using a StaticResourceConverter class:

public class StaticResourceConverter: BaseConverter, IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if(System.Convert.ToString(value).IsNullOrEmpty())
        {
            return null;
        }
        var resource = Application.Current.Resources[value];
        return resource;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new Exception("The method or operation is not implemented.");
    }
}

Binding to the Image object in MenuItem requires to create an instance of Image with an underlying DrawingImage using the same method:

<Style TargetType="{x:Type MenuItem}">
    <Setter Property="Icon">
        <Setter.Value>
            <Image Style="{StaticResource ResourceKey=SpMenuItemIcon}">
                <Image.Source>
                    <DrawingImage>
                        <DrawingImage.Drawing>
                            <GeometryDrawing
                                Brush="{StaticResource SpIconPath}"
                                Geometry="{Binding IconPath,
                                    Converter={converters:StaticResourceConverter}}"/>
                        </DrawingImage.Drawing>
                    </DrawingImage>
                </Image.Source>
            </Image>
        </Setter.Value>
    </Setter>
</Style>

The problem is that I suspect only one instance of Image is created or only 1 is bound as only the last icon in the menu is visible with the correct geometry path. Why WPF is binding only the last MenuItem and how to make it work with all menu items?

Update:

Just to help with testing here are few example geometry resources to bind to (using Key):

<Geometry x:Key="ExpandIconPath">F1 M 30.25,58L 18,58L 18,45.75L 22,41.75L 22,50.75L 30,42.75L 33.25,46L 25.25,54L 34.25,54L 30.25,58 Z M 58,45.75L 58,58L 45.75,58L 41.75,54L 50.75,54L 42.75,46L 46,42.75L 54,50.75L 54,41.75L 58,45.75 Z M 45.75,18L 58,18L 58,30.25L 54,34.25L 54,25.25L 46,33.25L 42.75,30L 50.75,22L 41.75,22L 45.75,18 Z M 18,30.25L 18,18L 30.25,18L 34.25,22L 25.25,22L 33.25,30L 30,33.25L 22,25.25L 22,34.25L 18,30.25 Z</Geometry>
<Geometry x:Key="CollapseIconPath">F1 M 54.2499,34L 42,34L 42,21.7501L 45.9999,17.7501L 45.9999,26.7501L 53.9999,18.7501L 57.2499,22.0001L 49.2499,30.0001L 58.2499,30.0001L 54.2499,34 Z M 34,21.7501L 34,34L 21.75,34L 17.75,30.0001L 26.75,30.0001L 18.75,22.0001L 22,18.7501L 30,26.7501L 30,17.7501L 34,21.7501 Z M 21.75,42L 34,42L 34,54.25L 30,58.25L 30,49.25L 22,57.25L 18.75,54L 26.75,46L 17.75,46L 21.75,42 Z M 42,54.25L 42,42L 54.2499,42L 58.2499,46L 49.2499,46.0001L 57.2499,54L 53.9999,57.25L 45.9999,49.25L 45.9999,58.25L 42,54.25 Z</Geometry>
<Geometry x:Key="CloseIconPath">F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z</Geometry>
like image 404
too Avatar asked Dec 15 '22 04:12

too


1 Answers

Unlike Clemens has suggested, you don't need to make the whole Style not shared. The problem comes from the Image which must be unique for each MenuItem. Therefore, you should make only Image not shared by putting it into resources with x:Shared set to False.

A working self-contained example:

MainWindow.xaml

<Window x:Class="So19902960WpfMenuItemIconGeometry.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:So19902960WpfMenuItemIconGeometry"
        Title="MainWindow" Height="350" Width="525">
    <Control.Resources>
        <local:StaticResourceConverter x:Key="StaticResourceConverter"/>
        <Geometry x:Key="ExpandIconPath">F1 M 30.25,58L 18,58L 18,45.75L 22,41.75L 22,50.75L 30,42.75L 33.25,46L 25.25,54L 34.25,54L 30.25,58 Z M 58,45.75L 58,58L 45.75,58L 41.75,54L 50.75,54L 42.75,46L 46,42.75L 54,50.75L 54,41.75L 58,45.75 Z M 45.75,18L 58,18L 58,30.25L 54,34.25L 54,25.25L 46,33.25L 42.75,30L 50.75,22L 41.75,22L 45.75,18 Z M 18,30.25L 18,18L 30.25,18L 34.25,22L 25.25,22L 33.25,30L 30,33.25L 22,25.25L 22,34.25L 18,30.25 Z</Geometry>
        <Geometry x:Key="CollapseIconPath">F1 M 54.2499,34L 42,34L 42,21.7501L 45.9999,17.7501L 45.9999,26.7501L 53.9999,18.7501L 57.2499,22.0001L 49.2499,30.0001L 58.2499,30.0001L 54.2499,34 Z M 34,21.7501L 34,34L 21.75,34L 17.75,30.0001L 26.75,30.0001L 18.75,22.0001L 22,18.7501L 30,26.7501L 30,17.7501L 34,21.7501 Z M 21.75,42L 34,42L 34,54.25L 30,58.25L 30,49.25L 22,57.25L 18.75,54L 26.75,46L 17.75,46L 21.75,42 Z M 42,54.25L 42,42L 54.2499,42L 58.2499,46L 49.2499,46.0001L 57.2499,54L 53.9999,57.25L 45.9999,49.25L 45.9999,58.25L 42,54.25 Z</Geometry>
        <Geometry x:Key="CloseIconPath">F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z</Geometry>
        <Image x:Key="MenuItemImage" x:Shared="False" Width="16" Height="16">
            <Image.Source>
                <DrawingImage>
                    <DrawingImage.Drawing>
                        <GeometryDrawing Brush="Blue"
                                Geometry="{Binding Tag,
                                    RelativeSource={RelativeSource AncestorType=MenuItem},
                                    Converter={StaticResource StaticResourceConverter}}"/>
                    </DrawingImage.Drawing>
                </DrawingImage>
            </Image.Source>
        </Image>
        <Style TargetType="MenuItem">
            <Setter Property="Icon" Value="{StaticResource MenuItemImage}"/>
        </Style>
    </Control.Resources>
    <Menu>
        <MenuItem Header="Expand" Tag="ExpandIconPath"/>
        <MenuItem Header="Collapse" Tag="CollapseIconPath"/>
        <MenuItem Header="Close" Tag="CloseIconPath"/>
    </Menu>
</Window>

MainWindow.xaml.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace So19902960WpfMenuItemIconGeometry
{
    public partial class MainWindow
    {
        public MainWindow ()
        {
            InitializeComponent();
        }
    }

    public class StaticResourceConverter : IValueConverter
    {
        public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == DependencyProperty.UnsetValue || value == null)
                return DependencyProperty.UnsetValue;
            return Application.Current.MainWindow.Resources[value];
        }

        public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
}

Notes:

  1. You should throw NotSupportedException instead of Exception with a custom message in the ConvertBack method. It is specifically meant for cases when a method is not and will not be implemented.

  2. You should check for DependencyProperty.UnsetValue in the Convert method. Converters should not crash the application when bindings fail.

like image 60
Athari Avatar answered Jan 07 '23 20:01

Athari