如何使用 ItemContainerStyle 设置 MenuItem 的图标

发布于 2024-08-12 06:00:12 字数 1046 浏览 4 评论 0原文

我按照此处将 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));
    }
}

我做错了什么?

I'm following the example here of binding a MenuItem to a data object.

<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>

It all works swimmingly except the MenuItem's icon shows up as the string System.Drawing.Bitmap. The bitmap in question is returned by the data object from a compiled resource.

internal static System.Drawing.Bitmap folder_page
{
    get
    {
        object obj = ResourceManager.GetObject("folder_page", resourceCulture);
        return ((System.Drawing.Bitmap)(obj));
    }
}

What am I doing wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

说谎友 2024-08-19 06:00:12

肯特(当然)有正确的答案。 但我想我应该继续发布从 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 链接

Kent (of course) has the right answer. But I thought I would go ahead and post the code for the converter that converts from the System.Drawing.Bitmap (Windows Forms) to a System.Windows.Windows.Media.BitmapSource (WPF) ... as this is a common problem/question.

This takes three steps:

  1. Use an image converter in your binding.
  2. Create the converter.
  3. Declare the converter in your resources.

Here is how you would use an image converter in your binding:

<Setter
    Property="MenuItem.Icon"
    Value="{Binding Path=Icon, Converter={StaticResource imageConverter}}"
/>

And, here is the code for the converter (put it into a file called ImageConverter.cs) and add it to your project:

[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);
}

Here is how you declare it in your resources section (note the local namespace that you will have to add):

<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>

And that's it!


Update

After doing a quick search for similar questions, I noticed that Lars Truijens pointed out here that the previous converter implementation leaks. I have updated the converter code above ... so that it doesn't leak.

For more information on the cause of the leak, see the remarks section on this MSDN link.

随遇而安 2024-08-19 06:00:12

WPF 使用 ImageSource 类,而不是 System.Drawing 类。您需要绑定到 ImageSource。您可以使用转换器将 Bitmap 转换为 ImageSource,或者您可以放弃资源并以不同的方式执行操作。

WPF works with ImageSources, not System.Drawing classes. You'll need to bind to an ImageSource. You could use a converter to convert your Bitmap to an ImageSource, or you could ditch the resources and do things differently.

我只土不豪 2024-08-19 06:00:12

WPF 的菜单项有些奇怪,因为它们ImageSource 对象,就像 WPF 框架的其余部分一样。

最简单的方法,这会给你带来最少的麻烦,就是在你的视图模型中有一个属性,返回一个完整的 Image 控件:

public Image MenuIcon
{
    get
    {
        return new Image()
               {
                   Source = CreateImageSource("myImage.png")
               };
    }
}

然后在菜单项的

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

有人可能会说这破坏了 MVVM 的精神,但在在某些时候,你只需要务实并继续解决更有趣的问题。

WPF's menuitems are somewhat weird in that they don't work with ImageSource objects like the rest of the WPF framework.

The easiest way, that will cause you the least amount of headache is to simply have a property in your viewmodel that returns a full Image control:

public Image MenuIcon
{
    get
    {
        return new Image()
               {
                   Source = CreateImageSource("myImage.png")
               };
    }
}

And then in your <Style> for menu items (which you can set in ItemContainerStyle for example) you simply bind the menu item's Icon property to the MenuIcon property in your viewmodel:

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

One could argue that this breaks the spirit of MVVM, but at some point you just have to be pragmatic and move on to more interesting problems.

猥︴琐丶欲为 2024-08-19 06:00:12

以下是我为菜单项创建 ViewModel 的方法: AbstractMenuItem。请特别注意 Icon 区域:

    #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 文件中的图片。

以下是我如何绑定到 工作台窗口

    <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>

Here's how I made a ViewModel for a menu item: AbstractMenuItem. Pay particular attention to the Icon region:

    #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

You just derive from this class and in the constructor you call SetIconFromBitmap and pass in a picture from your resx file.

Here's how I bound to those IMenuItems in the Workbench Window:

    <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>
浅紫色的梦幻 2024-08-19 06:00:12

对于后代:我想出了这个:

<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);
    }
}

For posterity: I've come up with this:

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

The converter is a combination of MarkupExtension and a IValueConverter, so you can specify it inline without having to make it a static resource.

It uses the System.Windows.Media.ImageSourceConverter to convert an uri to an ImageSource and then creates an Image control with that source.
As bonus it uses the serviceProvider parameter as supplied to ProvideValue so it can resolve relative image urls as WPF would do it.

[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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文