如何使用ItemContainerStyle设置MenuItem的图标

6
我在这里遵循一个将MenuItem绑定到数据对象的示例。
<Menu Grid.Row="0" KeyboardNavigation.TabNavigation="Cycle"
      ItemsSource="{Binding Path=MenuCommands}">  
    <Menu.ItemContainerStyle>
        <Style>
            <Setter Property="MenuItem.Header" Value="{Binding Path=DisplayName}"/>
            <Setter Property="MenuItem.ItemsSource" Value="{Binding Path=Commands}"/>
            <Setter Property="MenuItem.Command" Value="{Binding Path=Command}"/>
            <Setter Property="MenuItem.Icon" Value="{Binding Path=Icon}"/>
        </Style>
    </Menu.ItemContainerStyle>                
</Menu>

除了MenuItem的图标显示为字符串System.Drawing.Bitmap之外,一切都运作得很顺利。所涉及的位图是由编译资源的数据对象返回的。
internal static System.Drawing.Bitmap folder_page
{
    get
    {
        object obj = ResourceManager.GetObject("folder_page", resourceCulture);
        return ((System.Drawing.Bitmap)(obj));
    }
}

我做错了什么?


好问题...这是一个常见的问题。 - cplotts
5个回答

9
肯特(当然)给出了正确的答案。但是我想提供一个将System.Drawing.Bitmap(Windows Forms)转换为System.Windows.Windows.Media.BitmapSource(WPF)的转换器代码……因为这是一个常见的问题/疑问。
这需要三个步骤:
1. 在绑定中使用图像转换器。 2. 创建转换器。 3. 在资源中声明转换器。
以下是如何在绑定中使用图像转换器:
<Setter
    Property="MenuItem.Icon"
    Value="{Binding Path=Icon, Converter={StaticResource imageConverter}}"
/>

以下是转换器的代码(将其放入名为ImageConverter.cs的文件中),并将其添加到您的项目中:

[ValueConversion(typeof(Image), typeof(string))]
public class ImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        BitmapSource bitmapSource;

        IntPtr bitmap = ((Bitmap)value).GetHbitmap();
        try
        {
            bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(bitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
        }
        finally
        {
            DeleteObject(bitmap);
        }

        return bitmapSource;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }

    [DllImport("gdi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    static extern int DeleteObject(IntPtr o);
}

以下是如何在资源部分声明它的方式(请注意您需要添加的本地名称空间):

<Window
    x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication2"
>
    <Window.Resources>
        <local:ImageConverter x:Key="imageConverter"/>
    </Window.Resources>
    <!-- some xaml snipped for clarity -->
</Window>

就是这样了!


更新

在寻找类似问题的快速搜索之后,我注意到 Lars Truijens这里 指出之前的转换器实现有泄漏。我已经更新了上面的转换器代码...以便它不会泄漏。

有关泄漏原因的更多信息,请参见此 MSDN 链接 中的备注部分。


5

WPF使用ImageSource而不是System.Drawing类。您需要绑定到一个ImageSource。您可以使用转换器将您的Bitmap转换为ImageSource,或者您可以放弃资源并以不同的方式处理事情。


1
WPF的菜单项有些奇怪,它们与WPF框架的其余部分不同,无法使用ImageSource对象工作。
最简单的方法是在您的视图模型中拥有一个属性,该属性返回完整的Image控件,这将给您带来最少的头痛。
public Image MenuIcon
{
    get
    {
        return new Image()
               {
                   Source = CreateImageSource("myImage.png")
               };
    }
}

然后在菜单项的<Style>中(例如您可以在ItemContainerStyle中设置),您只需将菜单项的Icon属性绑定到视图模型中的MenuIcon属性:

<Setter Property="Icon" Value="{Binding MenuIcon}" />

有人可能会认为这违背了MVVM的精神,但在某些时候,你只需要务实地解决问题并继续处理更有趣的问题。


0

这是我如何为菜单项创建 ViewModel:AbstractMenuItem。 特别注意图标区域:

    #region " Icon "
    /// <summary>
    /// Optional icon that can be displayed in the menu item.
    /// </summary>
    public object Icon
    {
        get
        {
            if (IconFull != null)
            {
                System.Windows.Controls.Image img = new System.Windows.Controls.Image();
                if (EnableCondition.Condition)
                {
                    img.Source = IconFull;
                }
                else
                {
                    img.Source = IconGray;
                }
                return img;
            }
            else
            {
                return null;
            }
        }
    }
    private BitmapSource IconFull
    {
        get
        {
            return m_IconFull;
        }
        set
        {
            if (m_IconFull != value)
            {
                m_IconFull = value;
                if (m_IconFull != null)
                {
                    IconGray = ConvertFullToGray(m_IconFull);
                }
                else
                {
                    IconGray = null;
                }
                NotifyPropertyChanged(m_IconArgs);
            }
        }
    }
    private BitmapSource m_IconFull = null;
    static readonly PropertyChangedEventArgs m_IconArgs =
        NotifyPropertyChangedHelper.CreateArgs<AbstractMenuItem>(o => o.Icon);

    private BitmapSource IconGray { get; set; }

    private BitmapSource ConvertFullToGray(BitmapSource full)
    {
        FormatConvertedBitmap gray = new FormatConvertedBitmap();

        gray.BeginInit();
        gray.Source = full;
        gray.DestinationFormat = PixelFormats.Gray32Float;
        gray.EndInit();

        return gray;
    }

    /// <summary>
    /// This is a helper function so you can assign the Icon directly
    /// from a Bitmap, such as one from a resources file.
    /// </summary>
    /// <param name="value"></param>
    protected void SetIconFromBitmap(System.Drawing.Bitmap value)
    {
        BitmapSource b = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
            value.GetHbitmap(),
            IntPtr.Zero,
            Int32Rect.Empty,
            System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
        IconFull = b;
    }

    #endregion

你只需要从这个类派生,然后在构造函数中调用SetIconFromBitmap并传入来自resx文件的图片。

以下是我如何绑定到工作台窗口中的那些IMenuItems:

    <Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=(local:Workbench.MainMenu)}">
        <Menu.ItemContainerStyle>
            <Style>
                <Setter Property="MenuItem.Header" Value="{Binding Path=(contracts:IMenuItem.Header)}"/>
                <Setter Property="MenuItem.ItemsSource" Value="{Binding Path=(contracts:IMenuItem.Items)}"/>
                <Setter Property="MenuItem.Icon" Value="{Binding Path=(contracts:IMenuItem.Icon)}"/>
                <Setter Property="MenuItem.IsCheckable" Value="{Binding Path=(contracts:IMenuItem.IsCheckable)}"/>
                <Setter Property="MenuItem.IsChecked" Value="{Binding Path=(contracts:IMenuItem.IsChecked)}"/>
                <Setter Property="MenuItem.Command" Value="{Binding}"/>
                <Setter Property="MenuItem.Visibility" Value="{Binding Path=(contracts:IControl.Visible), 
                    Converter={StaticResource BooleanToVisibilityConverter}}"/>
                <Setter Property="MenuItem.ToolTip" Value="{Binding Path=(contracts:IControl.ToolTip)}"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=(contracts:IMenuItem.IsSeparator)}" Value="true">
                        <Setter Property="MenuItem.Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type MenuItem}">
                                    <Separator Style="{DynamicResource {x:Static MenuItem.SeparatorStyleKey}}"/>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Menu.ItemContainerStyle>
    </Menu>

0

为了后人留存:我想到了这个:

<Menu.ItemContainerStyle>
    <Style TargetType="MenuItem">
        <Setter Property="Icon" Value="{Binding IconUrl, Converter={ns:UrlToImageConverter Width=16, Height=16}}"/>
    </Style>
</Menu.ItemContainerStyle>

转换器是MarkupExtensionIValueConverter的组合,因此您可以在线指定它,而不必将其变成静态资源。

它使用System.Windows.Media.ImageSourceConverter将uri转换为ImageSource,然后创建一个具有该源的Image控件。 作为奖励,它使用提供给ProvideValueserviceProvider参数,因此它可以像WPF一样解析相对图像url。

[ValueConversion(typeof(string), typeof(Image))]
[ValueConversion(typeof(Uri), typeof(Image))]
public class UrlToImageConverter : MarkupExtension, IValueConverter
{
    public int? MaxWidth { get; set; }

    public int? MaxHeight { get; set; }

    public int? MinWidth { get; set; }

    public int? MinHeight { get; set; }

    public Stretch? Stretch { get; set; }

    public StretchDirection? StretchDirection { get; set; }

    private static readonly ImageSourceConverter _converter = new System.Windows.Media.ImageSourceConverter();

    private readonly IServiceProvider _serviceProvider;

    public UrlToImageConverter()
    {
        _serviceProvider = new ServiceContainer();
    }

    /// <summary>  </summary>
    private UrlToImageConverter(UrlToImageConverter provider, IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider ?? new ServiceContainer();
        MaxWidth = provider.MaxWidth;
        MaxHeight = provider.MaxHeight;
        MinWidth = provider.MinWidth;
        MinHeight = provider.MinHeight;
        Stretch = provider.Stretch;
        StretchDirection = provider.StretchDirection;
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return null;

        var context = GetTypeDescriptorContext();

        bool canConvert;
        if (context == null)
            canConvert = _converter.CanConvertFrom(value.GetType());
        else
            canConvert = _converter.CanConvertFrom(context, value.GetType());

        if (canConvert)
        {
            if (context == null)
                value = _converter.ConvertFrom(value);
            else
                value = _converter.ConvertFrom(context, CultureInfo.CurrentCulture, value);

            if (value is ImageSource source)
            {
                var img = new Image { Source = source };
                if (MaxWidth != null) img.MaxWidth = MaxWidth.Value;
                if (MaxHeight != null) img.MaxHeight = MaxHeight.Value;
                if (MinWidth != null) img.MinWidth = MinWidth.Value;
                if (MinHeight != null) img.MinHeight = MinHeight.Value;                    
                img.Stretch = Stretch ?? System.Windows.Media.Stretch.Uniform;
                img.StretchDirection = StretchDirection ?? System.Windows.Controls.StretchDirection.Both;
                return img;
            }
        }

        return null;
    }

    private ITypeDescriptorContext GetTypeDescriptorContext()
    {
        if (_serviceProvider is ITypeDescriptorContext context)
            return context;
        else
            return (ITypeDescriptorContext)_serviceProvider?.GetService(typeof(ITypeDescriptorContext));
    }

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

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return new UrlToImageConverter(this, serviceProvider);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接