绑定资源键,WPF

18

我有一个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>

现在,在显示项目时:

  • 文本块显示FolderItemImage
  • 显示第一张图片
  • 第二张图片不显示。

整个想法是将项目图像设置为存储在资源中的图像,但上面介绍的技术不幸地行不通,现在我知道原因了:

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

结果:

未处理的类型为 'System.Windows.Markup.XamlParseException' 的异常在 PresentationFramework.dll 中发生。

附加信息:无法在类型为 'DynamicResourceExtension' 的 'ResourceKey' 属性上设置 'Binding'。'Binding' 只能设置在 DependencyObject 的 DependencyProperty 上。

因此,我需要重新表达我的问题:如何将模型中存储的一些数据(例如资源键)转换为动态资源?它必须是动态资源,因为我确定它可能会在运行时更改。


你从哪儿得到使用这个的想法的:{DynamicResource {Binding ImageResourceKey}}。我以前从没见过这个,而且看起来不对,为什么不直接用这个:{Binding ImageResourceKey}。请展示你的 ImageResourceKey 定义。 - Sheridan
1
因为ImageResourceKey是一个字符串,作为DynamicResource的键。ImageResourceKey的定义是:string ImageResourceKey { get; set; } :) - Spook
1
+1 始终问那些深入有趣的问题。 - Sheridan
4个回答

22

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

干得好,谢谢你。这个完美地运行起来了,开箱即用。 - Alexis
2
非常棒的工作,我已经在生产中使用了,只需要稍微修改一下,我想提出一个编辑建议。当我第一次使用时,我使用了枚举作为源,所以它没有起作用,因为资源键始终(?)是字符串 - 所以我需要将第45行修改如下,才能使其正常工作:target.SetResourceReference(dp, newVal.Item1.ToString()); - FastJack
除了像之前评论中使用ToString一样,我还添加了对FrameworkContentElement的支持,因为它具有与未继承的SetBinding相同签名的方法。 - Grault

14

不能直接完成。不过,还有一种方法涉及附加属性:

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

1
这太棒了,完全符合我过去几个小时一直在寻找的东西。谢谢! - Itzalive

7

我认为你试图以这种方式使用动态字符串值作为字典键是不可能的。

你需要制作一个将string转换为ImageSourceConverter,或者使用DataTrigger根据ImageResourceKey选择Source

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

未测试


唯一的问题是,它应该作为DynamicResource工作-如果资源更改,则刷新资源。这可行吗?否则。 - Spook
嗯,我从未尝试过这个,但你可以看看 DynamicResourceExtensionConverter - 它似乎可以帮助。 - Adassko
您可能可以通过使用以下代码在代码后台完成它:iIcon.SetResourceReference(Image.SourceProperty, ImageResourceKey),或者您仍然可以使用DataTrigger - Adassko

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

使用方法

<Image Source="{local:DynamicResourceBinding ImageResourceKey}"/>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接