绑定到资源键,WPF

时间:2013-12-13 10:59:45

标签: c# wpf binding dynamicresource

我有一些带有一些图像的ResourceDictionary:

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

现在,当显示项目时:

  • Textblock显示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上设置。

所以我必须重新解释一下我的问题:如何将模型中存储的某些数据(可能是?)转换为动态资源? 是一个动态资源,因为我确信它可能会在运行时发生变化。

4 个答案:

答案 0 :(得分:15)

我编写以下标记扩展名以允许在一般情况下绑定到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}"/>

答案 1 :(得分:8)

不能直接完成。还有另一种方法涉及附属物:

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}" />

答案 2 :(得分:4)

我不认为可以使用动态字符串值作为字典的键,而不是尝试这样做。

您需要制作一个Converter,将string转换为ImageSource或使用DataTrigger选择Source,具体取决于ImageResourceKey

使用Converter:

资源中的

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

未经测试

答案 3 :(得分: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>();
}

用法

&lt; Image Source =&#34; {local:DynamicResourceBinding ImageResourceKey}&#34; /&gt;