2013-12-13 108 views
8

我有一些圖片資源字典:結合資源鍵,WPF

<BitmapImage UriSource="..\Images\Bright\folder-bright.png" 
      x:Key="FolderItemImage" /> 

我已經創建樹形視圖項HierarchicalTemplate類似如下:

<HierarchicalDataTemplate ItemsSource="{Binding VisibleChildren}" 
          DataType="{x:Type st:StructureTreeItem}"> 
    <StackPanel Orientation="Horizontal"> 
     <TextBlock Text="{Binding ImageResourceKey}" /> 
     <Image x:Name="iIcon2" Source="{DynamicResource FolderItemImage}"/> 
     <Image x:Name="iIcon" 
       Source="{DynamicResource {Binding ImageResourceKey}}"/> 
    </StackPanel> 
</HierarchicalDataTemplate> 

現在,顯示項目時:

  • 正文塊顯示FolderItemImage
  • 顯示第一張圖片
  • 第二張圖片未顯示。

整個想法是項圖像設置爲存儲在資源的,但不幸的是上面介紹的技術將無法正常工作,現在我知道了,爲什麼:

<Image x:Name="iIcon3" Width="16" Height="16" Margin="0, 1, 3, 1" > 
    <Image.Source> 
     <DynamicResource ResourceKey="{Binding ImageResourceKey}" /> 
    </Image.Source> 
</Image> 

結果:

在PresentationFramework.dll中發生未處理的「System.Windows.Markup.XamlParseException」異常異常

附加信息:無法在'DynamicResourceExtension'類型的'ResourceKey'屬性上設置'綁定'。 '綁定'只能在DependencyObject的DependencyProperty上設置。

所以我不得不改變我的問題:如何將模型中存儲的某些數據(資源鍵,也許?)轉換爲動態資源?它是一個動態資源,因爲我確定它可能會在運行時更改。

+0

你從哪兒弄來的主意,用這個?:'{DynamicResource {結合ImageResourceKey}}'。我從來沒有見過,它看起來不正確,所以爲什麼不使用這個?:'{Binding ImageResourceKey}'。請顯示您的'ImageResourceKey'的定義。 – Sheridan

+0

因爲ImageResourceKey是一個字符串,它是DynamicResource的一個鍵。 ImageResourceKey的定義是:'string ImageResourceKey {get;組; }':) – Spook

+0

+1永遠帶着深刻而有趣的問題。 – Sheridan

回答

7

它不能直接完成。還有另外一種方式,雖然涉及的附加屬性:

public static class ImageHelper { 

    private static void SourceResourceKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { 

     var element = d as Image; 
     if (element != null) { 

      element.SetResourceReference(Image.SourceProperty, e.NewValue); 
     } 
    } 

    public static readonly DependencyProperty SourceResourceKeyProperty = DependencyProperty.RegisterAttached("SourceResourceKey", 
     typeof(object), 
     typeof(ImageHelper), 
     new PropertyMetadata(String.Empty, SourceResourceKeyChanged)); 

    public static void SetSourceResourceKey(Image element, object value) { 

     element.SetValue(SourceResourceKeyProperty, value); 
    } 

    public static object GetSourceResourceKey(Image element) { 

     return element.GetValue(SourceResourceKeyProperty); 
    } 
} 

然後:

<Image local:ImageHelper.SourceResourceKey="{Binding SomeValue}" /> 
+0

這真是太棒了,這正是我花了幾個小時尋找的。謝謝! – Itzalive

4

我不認爲可以使用動態字符串值作爲字典的關鍵字,這是您嘗試執行此操作的一種方式。

您需要可以使Converter從一個string轉換爲ImageSource或使用DataTrigger選擇Source取決於ImageResourceKey

使用轉換器:

資源:

<local:StringToResource x:Key="StringToResource" /> 

則:

<Image x:Name="iIcon" Source="{Binding ImageResourceKey, Converter={StaticResource StringToResource}}"/> 

您的轉換可能看起來像:

public class StringToResource: IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     return Application.Current.FindResource(value as string); 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     throw new NotImplementedException(); 
    } 
} 

沒有測試

+0

唯一的問題是,它應該作爲DynamicResource工作 - 刷新資源,如果它改變。它可以實現嗎?否則 – Spook

+0

嗯,我從來沒有嘗試過這個,但你可以看看'DynamicResourceExtensionConverter' - 它看起來像它可以幫助 – Adassko

+0

你可以在代碼執行中使用:'iIcon.SetResourceReference(Image.SourceProperty,ImageResourceKey )'或者你仍然可以使用'DataTrigger' – Adassko

12

我寫下面的標記的擴展,允許結合的ResourceKey在一般情況下

using System; 
using System.ComponentModel; 
using System.Globalization; 
using System.Windows; 
using System.Windows.Data; 
using System.Windows.Markup; 

namespace Mersoft.Mvvm.MarkupExtensions 
{ 

    public class ResourceBinding : MarkupExtension 
    { 
     #region Helper properties 

     public static object GetResourceBindingKeyHelper(DependencyObject obj) 
     { 
      return (object)obj.GetValue(ResourceBindingKeyHelperProperty); 
     } 

     public static void SetResourceBindingKeyHelper(DependencyObject obj, object value) 
     { 
      obj.SetValue(ResourceBindingKeyHelperProperty, value); 
     } 

     // Using a DependencyProperty as the backing store for ResourceBindingKeyHelper. This enables animation, styling, binding, etc... 
     public static readonly DependencyProperty ResourceBindingKeyHelperProperty = 
      DependencyProperty.RegisterAttached("ResourceBindingKeyHelper", typeof(object), typeof(ResourceBinding), new PropertyMetadata(null, ResourceKeyChanged)); 

     static void ResourceKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
     { 
      var target = d as FrameworkElement; 
      var newVal = e.NewValue as Tuple<object, DependencyProperty>; 

      if (target == null || newVal == null) 
       return; 

      var dp = newVal.Item2; 

      if (newVal.Item1 == null) 
      { 
       target.SetValue(dp, dp.GetMetadata(target).DefaultValue); 
       return; 
      } 

      target.SetResourceReference(dp, newVal.Item1); 

     } 

     #endregion 

     public ResourceBinding() 
     { 

     } 

     public ResourceBinding(string path) 
     { 
      this.Path = new PropertyPath(path); 
     } 

     public override object ProvideValue(IServiceProvider serviceProvider) 
     { 
      var provideValueTargetService = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); 
      if (provideValueTargetService == null) 
       return null; 

      if (provideValueTargetService.TargetObject != null && 
       provideValueTargetService.TargetObject.GetType().FullName == "System.Windows.SharedDp") 
       return this; 


      var targetObject = provideValueTargetService.TargetObject as FrameworkElement; 
      var targetProperty = provideValueTargetService.TargetProperty as DependencyProperty; 
      if (targetObject == null || targetProperty == null) 
       return null; 



      var binding = new Binding(); 

      #region binding 

      binding.Path = this.Path; 
      binding.XPath = this.XPath; 
      binding.Mode = this.Mode; 
      binding.UpdateSourceTrigger = this.UpdateSourceTrigger; 
      binding.Converter = this.Converter; 
      binding.ConverterParameter = this.ConverterParameter; 
      binding.ConverterCulture = this.ConverterCulture; 

      if (this.RelativeSource != null) 
       binding.RelativeSource = this.RelativeSource; 

      if (this.ElementName != null) 
       binding.ElementName = this.ElementName; 

      if (this.Source != null) 
       binding.Source = this.Source; 

      binding.FallbackValue = this.FallbackValue; 

      #endregion 

      var multiBinding = new MultiBinding(); 
      multiBinding.Converter = HelperConverter.Current; 
      multiBinding.ConverterParameter = targetProperty; 

      multiBinding.Bindings.Add(binding); 

      multiBinding.NotifyOnSourceUpdated = true; 

      targetObject.SetBinding(ResourceBindingKeyHelperProperty, multiBinding); 

      return null; 

     } 


     #region Binding Members 

     /// <summary> The source path (for CLR bindings).</summary> 
     public object Source 
     { 
      get; 
      set; 
     } 

     /// <summary> The source path (for CLR bindings).</summary> 
     public PropertyPath Path 
     { 
      get; 
      set; 
     } 

     /// <summary> The XPath path (for XML bindings).</summary> 
     [DefaultValue(null)] 
     public string XPath 
     { 
      get; 
      set; 
     } 

     /// <summary> Binding mode </summary> 
     [DefaultValue(BindingMode.Default)] 
     public BindingMode Mode 
     { 
      get; 
      set; 
     } 

     /// <summary> Update type </summary> 
     [DefaultValue(UpdateSourceTrigger.Default)] 
     public UpdateSourceTrigger UpdateSourceTrigger 
     { 
      get; 
      set; 
     } 

     /// <summary> The Converter to apply </summary> 
     [DefaultValue(null)] 
     public IValueConverter Converter 
     { 
      get; 
      set; 
     } 

     /// <summary> 
     /// The parameter to pass to converter. 
     /// </summary> 
     /// <value></value> 
     [DefaultValue(null)] 
     public object ConverterParameter 
     { 
      get; 
      set; 
     } 

     /// <summary> Culture in which to evaluate the converter </summary> 
     [DefaultValue(null)] 
     [TypeConverter(typeof(System.Windows.CultureInfoIetfLanguageTagConverter))] 
     public CultureInfo ConverterCulture 
     { 
      get; 
      set; 
     } 

     /// <summary> 
     /// Description of the object to use as the source, relative to the target element. 
     /// </summary> 
     [DefaultValue(null)] 
     public RelativeSource RelativeSource 
     { 
      get; 
      set; 
     } 

     /// <summary> Name of the element to use as the source </summary> 
     [DefaultValue(null)] 
     public string ElementName 
     { 
      get; 
      set; 
     } 


     #endregion 

     #region BindingBase Members 

     /// <summary> Value to use when source cannot provide a value </summary> 
     /// <remarks> 
     ///  Initialized to DependencyProperty.UnsetValue; if FallbackValue is not set, BindingExpression 
     ///  will return target property's default when Binding cannot get a real value. 
     /// </remarks> 
     public object FallbackValue 
     { 
      get; 
      set; 
     } 

     #endregion 



     #region Nested types 

     private class HelperConverter : IMultiValueConverter 
     { 
      public static readonly HelperConverter Current = new HelperConverter(); 

      public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
      { 
       return Tuple.Create(values[0], (DependencyProperty)parameter); 
      } 
      public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
      { 
       throw new NotImplementedException(); 
      } 
     } 

     #endregion 
    } 
} 

使用

<Image Source="{local:ResourceBinding ImageResourceKey}"/> 
+0

好的工作,謝謝。這完美的,開箱即用。 – Alexis

1
public class DynamicResourceBinding : MarkupExtension 
{ 
    public DynamicResourceBinding(string path) 
    { 
     binding = new Binding(path); 
    } 

    #region Binding Members 

    public PropertyPath Path 
    { 
     get { return binding.Path; } 
     set { binding.Path = value; } 
    } 
    public string XPath 
    { 
     get { return binding.XPath; } 
     set { binding.XPath = value; } 
    } 
    [DefaultValue(BindingMode.Default)] 
    public BindingMode Mode 
    { 
     get { return binding.Mode; } 
     set { binding.Mode = value; } 
    } 
    [DefaultValue(UpdateSourceTrigger.Default)] 
    public UpdateSourceTrigger UpdateSourceTrigger 
    { 
     get { return binding.UpdateSourceTrigger; } 
     set { binding.UpdateSourceTrigger = value; } 
    } 
    public IValueConverter Converter 
    { 
     get { return binding.Converter; } 
     set { binding.Converter = value; } 
    } 
    public object ConverterParameter 
    { 
     get { return binding.ConverterParameter; } 
     set { binding.ConverterParameter = value; } 
    } 
    [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))] 
    public CultureInfo ConverterCulture 
    { 
     get { return binding.ConverterCulture; } 
     set { binding.ConverterCulture = value; } 
    } 
    public object Source 
    { 
     get { return binding.Source; } 
     set { binding.Source = value; } 
    } 
    public string ElementName 
    { 
     get { return binding.ElementName; } 
     set { binding.ElementName = value; } 
    } 
    public RelativeSource RelativeSource 
    { 
     get { return binding.RelativeSource; } 
     set { binding.RelativeSource = value; } 
    } 
    public object FallbackValue 
    { 
     get { return binding.FallbackValue; } 
     set { binding.FallbackValue = value; } 
    } 

    private readonly Binding binding; 

    #endregion Binding Members 

    public override object ProvideValue(IServiceProvider serviceProvider) 
    { 
     var provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; 
     if (provideValueTarget != null) 
     { 
      var targetObject = provideValueTarget.TargetObject as FrameworkElement; 
      if (targetObject != null) 
      { 
       var targetProperty = provideValueTarget.TargetProperty as DependencyProperty; 
       if (targetProperty != null) 
       { 
        targetObject.SetBinding(EnsureResourceKeyProperty(targetProperty), binding); 
       } 
      } 
     } 

     return null; 
    } 

    private static readonly object locker = new object(); 

    public static DependencyProperty EnsureResourceKeyProperty(DependencyProperty targetProperty) 
    { 
     DependencyProperty resourceKeyProperty; 
     lock (locker) 
     { 
      if (!DirectMap.TryGetValue(targetProperty, out resourceKeyProperty)) 
      { 
       resourceKeyProperty = RegisterResourceKeyProperty(targetProperty); 
       DirectMap.Add(targetProperty, resourceKeyProperty); 
       ReverseMap.Add(resourceKeyProperty, targetProperty); 
      } 
     } 
     return resourceKeyProperty; 
    } 

    private static DependencyProperty RegisterResourceKeyProperty(DependencyProperty targetProperty) 
    { 
     return DependencyProperty.RegisterAttached(targetProperty.Name + "_ResourceKey", typeof(object), typeof(DynamicResourceBinding), 
      new PropertyMetadata(ResourceKeyChanged)); 
    } 

    private static void ResourceKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var fe = d as FrameworkElement; 
     if (fe != null) 
     { 
      lock (locker) 
      { 
       DependencyProperty targetProperty; 
       if (ReverseMap.TryGetValue(e.Property, out targetProperty)) 
       { 
        fe.SetResourceReference(targetProperty, e.NewValue); 
       } 
      } 
     } 
    } 

    private static readonly Dictionary<DependencyProperty, DependencyProperty> DirectMap = new Dictionary<DependencyProperty, DependencyProperty>(); 
    private static readonly Dictionary<DependencyProperty, DependencyProperty> ReverseMap = new Dictionary<DependencyProperty, DependencyProperty>(); 
} 

使用

<圖片來源= 「{本地:DynamicResourceBinding ImageResourceKey}」/ >