Images in XAML ResourceDictionary disappear on Too

2019-03-26 15:53发布

问题:

I have started to move various common Images into a ResourceDictionary and noticed an odd behavior in my WPF application. If the Image is used in a MenuItem and in a Button on a ToolBar, when I open the Menu the image disappears on the Button.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Image x:Key="NewImage"
           Source="/SomeApplication;component/Resources/NewDocumentHS.png"
           Stretch="None"/>
    <!-- ... -->

Relevant XAML from the Window:

<Menu>
    <MenuItem Header="_File">
        <MenuItem Header="_New"
                  Command="{Binding NewCommand}"
                  Icon="{DynamicResource NewImage}" />
<!-- ... -->
<ToolBarTray>
    <ToolBar>
        <Button Command="{Binding NewCommand}"
                Content="{DynamicResource NewImage}" />

I assume this is a caveat of resources in a ResourceDictionary, but I am unable to discover the appropriate fix for this. Behavior occurs with both StaticResource and DynamicResource. It also doesn't appear to be affected by if the ResourceDictionary stands on its own or if it is merged with others. No other resource shares that key either.

Edit: Additionally, adding PresentationOptions:Freeze="True" to the images did not change the situation.

回答1:

The Image class is a visual, so it can only appear in the visual tree in one location. Therefore, you cannot share it among multiple MenuItems/Buttons/etc.

You can however share the ImageSource (i.e. Image.Source) value.

In WPF, I believe you can use x:Shared="False" to force WPF to create a new instance for each request though.



回答2:

You cannot use an Image control in multiple places, it can only be appear in the Visual Tree at one place, so if the call to the resource is made the image is being snatched from the previous owner.

Edit: x:Shared="False" Is obviously a better solution than all of my suggestions below, i wonder why such an important property does not show up in the Intellisense -_-


This behaviour is a bit of a pain, i normally use to predefine a IconStyle and the BitmapImages for the Source of the images but create new Images for every MenuItem where i might need it.

You can also create a DataTemplate for your Icon:

Resources:

    <Style x:Key="IconImageStyle" TargetType="{x:Type Image}">
        <Setter Property="MaxWidth" Value="16"/>
        <Setter Property="MaxHeight" Value="16"/>
    </Style>
    <DataTemplate x:Key="Icon_Close_Template">
        <Image Style="{StaticResource IconImageStyle}"
               Source="pack://application:,,,/Images/Close.ico"/>
    </DataTemplate>

Usage:

<Menu>
    <MenuItem Header="File">
        <MenuItem Header="Close">
            <MenuItem.Icon>
                <ContentPresenter ContentTemplate="{StaticResource Icon_Close_Template}"/>
            </MenuItem.Icon>
        </MenuItem>
        <MenuItem Header="Close">
            <MenuItem.Icon>
                <ContentPresenter ContentTemplate="{StaticResource Icon_Close_Template}"/>
            </MenuItem.Icon>
        </MenuItem>
    </MenuItem>
</Menu>

Since templates are created via factories this will work, still significantly inflates the XAML though...

To get around this you could for example write a markup extension, this one is very simple and only copies the values of the Source and Style properties, you could also use reflection or other means to create a complete copy:

[MarkupExtensionReturnType(typeof(object))]
public class IconExtension : MarkupExtension
{
    private Image icon;
    public Image Icon
    {
        get { return icon; }
        set { icon = value; }
    }

    public IconExtension() { }
    public IconExtension(Image icon)
    {
        Icon = icon;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (Icon == null) throw new ArgumentNullException("Icon");
        return new Image() { Source = Icon.Source, Style = Icon.Style };
    }
}

Can be used like this:

<Style x:Key="IconImageStyle" TargetType="{x:Type Image}">
    <Setter Property="MaxWidth" Value="16"/>
    <Setter Property="MaxHeight" Value="16"/>
</Style>
<Image x:Key="Icon_Close"  Style="{StaticResource IconImageStyle}" Source="pack://application:,,,/Images/Close.ico"/>
<!-- ... -->
<MenuItem Header="File">
    <MenuItem Header="Close" Icon="{m:Icon {StaticResource Icon_Close}}"/>
    <MenuItem Header="Close" Icon="{m:Icon {StaticResource Icon_Close}}"/>
</MenuItem>