如何使用ItemContainerStyle设置MenuItem的图标

时间:2009-11-24 12:28:59

标签: wpf data-binding mvvm icons menuitem

我正在遵循将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));
    }
}

我做错了什么?

5 个答案:

答案 0 :(得分:9)

肯特(当然)有正确的答案。 但我想我会继续发布转换器的代码,该转换器从System.Drawing.Bitmap(Windows窗体)转换为System.Windows.Windows.Media.BitmapSource(WPF)。因为这是一个常见的问题/问题。

这需要三个步骤:

  1. 在装订中使用图像转换器。
  2. 创建转换器。
  3. 在您的资源中声明转换器。
  4. 以下是如何在绑定中使用图像转换器

    <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指出here前一个转换器实现泄漏。我已经更新了上面的转换器代码......这样它就不会泄漏。

    有关泄漏原因的详细信息,请参阅此MSDN上的备注部分link

答案 1 :(得分:5)

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

答案 2 :(得分:1)

WPF的menuitems有些奇怪,因为他们使用ImageSource对象,就像WPF框架的其余部分一样。

最容易让您头痛的方法就是在viewmodel中添加一个返回完整Image控件的属性:

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

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

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

有人可能会说这打破了MVVM的精神,但在某些时候你必须务实并转向更有趣的问题。

答案 3 :(得分:0)

以下是我为菜单项制作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文件传入图片。

以下是我如何绑定Workbench Window中的IMenuItem:

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

答案 4 :(得分: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控件。 作为奖励,它使用提供给serviceProvider的{​​{1}}参数,因此它可以解析相对图像网址,因为WPF会这样做。

ProvideValue