2009-11-24 52 views
5

我正在关注将MenuItem绑定到数据对象的示例。如何使用ItemContainerStyle设置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)); 
    } 
} 

我在做什么错?

+0

好问题......这是一个常见问题。 – cplotts 2010-04-21 16:44:17

回答

5

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

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

肯特(当然)有正确的答案。 但我想我会继续并发布转换器的代码,从System.Drawing.Bitmap(Windows窗体)转换为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指出here,以前的转换器实现泄漏。我已经更新了上面的转换器代码...以便它不泄漏。

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

1

WPF的的菜单项是在有些怪异,他们工作,ImageSource对象,如WPF框架的其余部分。

最简单的方法,这将导致你头痛的最低金额是简单地在你的视图模型,返回一个完整的Image控制属性:

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

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

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

一凑ld认为这打破了MVVM的精神,但在某些时候,你只需要务实,并转向更有趣的问题。

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参数提供给ProvideValue,因此它可以解析相关的图像URL,因为WPF会这样做。

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