【问题标题】:How do I set MenuItem's Icon using ItemContainerStyle如何使用 ItemContainerStyle 设置 MenuItem 的图标
【发布时间】:2009-11-24 12:28:58
【问题描述】:

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

我做错了什么?

【问题讨论】:

  • 好问题...这是一个常见问题。

标签: wpf data-binding mvvm icons menuitem


【解决方案1】:

肯特(当然)有正确的答案。 但我想我会继续发布从 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 上的备注部分。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      WPF 的菜单项有点奇怪,因为它们像 WPF 框架的其余部分一样与 ImageSource 对象一起使用。

      最简单的方法是让您的视图模型中的属性返回完整的Image 控件:

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

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

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

      有人可能会争辩说,这违背了 MVVM 的精神,但在某些时候,你只需要务实并转向更有趣的问题。

      【讨论】:

        【解决方案4】:

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

        这是我绑定到Workbench Window 中的那些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>
        

        【讨论】:

          【解决方案5】:

          为了后代:我想出了这个:

          <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 的参数,因此它可以像 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);
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2015-11-04
            • 1970-01-01
            • 1970-01-01
            • 2016-01-07
            • 1970-01-01
            • 2022-09-30
            • 2012-12-23
            • 1970-01-01
            相关资源
            最近更新 更多