2009-11-24 56 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); 
    } 
}