【问题标题】:How do I duplicate a resource reference in code behind in WPF?如何在 WPF 中的代码中复制资源引用?
【发布时间】:2015-02-03 00:57:29
【问题描述】:

在我的应用程序中,我有一个颜色资源。我有一个元素使用该颜色作为 xaml 中的动态资源。

  <Window x:Class="ResourcePlay.MainWindow"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          Title="MainWindow" Height="350" Width="425">
     <Window.Resources>
        <Color x:Key="MyColor">Red</Color>
     </Window.Resources>
     <Grid>
        <Rectangle VerticalAlignment="Top" Width="80" Height="80" Margin="10">
           <Rectangle.Fill>
              <SolidColorBrush x:Name="TopBrush" Color="{DynamicResource MyColor}"/>
           </Rectangle.Fill>
        </Rectangle>
        <Rectangle VerticalAlignment="Bottom" Width="80" Height="80" Margin="10">
           <Rectangle.Fill>
              <SolidColorBrush x:Name="BottomBrush"/>
           </Rectangle.Fill>
        </Rectangle>
     </Grid>
  </Window>

在代码中,我想复制这个资源引用。

  using System.Windows;
  using System.Windows.Media;

  namespace ResourcePlay {
     public partial class MainWindow : Window {
        public MainWindow() {
           InitializeComponent();

           // I want to copy the resource reference, not the color.
           BottomBrush.Color = TopBrush.Color;

           // I'd really rather do something like this.
           var reference = TopBrush.GetResourceReference(SolidColorBrush.ColorProperty);
           BottomBrush.SetResourceReference(reference);

           // I want this to change the colors of both elements
           Resources["MyColor"] = Colors.Green;
        }
     }
  }

但是,SetResourceReference 仅适用于 FrameworkElements 或 FrameworkContentElements。 SolidColorBrush 只是一个 Freezable。另外,我不知道如何在后面的代码中获取资源引用。

有没有办法在 WPF 中做到这一点,以便两种颜色同时改变?在我的实际应用程序中,问题并不是那么简单,所以我不能只在 xaml 中添加第二个 DynamicResource。

【问题讨论】:

  • 请详细解释为什么您不简单地将SolidColorBrush 声明为资源本身,然后将其用于所需元素的Fill 属性。
  • @PeterDuniho 这里的 SolidColorBrush 是我在应用程序中使用的自定义 Freezable 子类的代理。真正的对象是为依赖注入参数化的,所以我不能在 xaml 中创建对象。
  • 不知道为什么有人会否决一个问题,因为这也是我的确切问题。这个帖子为我提供了一个解决方案。

标签: c# wpf xaml dynamicresource


【解决方案1】:

Il Vic 建议使用反射。在此基础上进行扩展,我能够为 DependencyObject 构建一些扩展方法来满足我的需求。我真的不喜欢在代码中使用反射,如果其他人知道实现这一点的更好方法,我很乐意看到它。至少当我尝试从后面的代码调试 DynamicResources 时,这会有所帮助。

  public static class DependencyObjectExtensions
  {
     public static object GetDynamicResourceKey(this DependencyObject obj, DependencyProperty prop)
     {
        // get the value entry from the depencency object for the specified dependency property
        var dependencyObject = typeof(DependencyObject);
        var dependencyObject_LookupEntry = dependencyObject.GetMethod("LookupEntry", BindingFlags.NonPublic | BindingFlags.Instance);
        var entryIndex = dependencyObject_LookupEntry.Invoke(obj, new object[] { prop.GlobalIndex });
        var effectiveValueEntry_GetValueEntry = dependencyObject.GetMethod("GetValueEntry", BindingFlags.NonPublic | BindingFlags.Instance);
        var valueEntry = effectiveValueEntry_GetValueEntry.Invoke(obj, new object[] { entryIndex, prop, null, 0x10 });

        // look inside the value entry to find the ModifiedValue object
        var effectiveValueEntry = valueEntry.GetType();
        var effectiveValueEntry_Value = effectiveValueEntry.GetProperty("Value", BindingFlags.Instance | BindingFlags.NonPublic);
        var effectiveValueEntry_Value_Getter = effectiveValueEntry_Value.GetGetMethod(nonPublic: true);
        var rawEntry = effectiveValueEntry_Value_Getter.Invoke(valueEntry, new object[0]);

        // look inside the ModifiedValue object to find the ResourceReference
        var modifiedValue = rawEntry.GetType();
        var modifiedValue_BaseValue = modifiedValue.GetProperty("BaseValue", BindingFlags.Instance | BindingFlags.NonPublic);
        var modifiedValue_BaseValue_Getter = modifiedValue_BaseValue.GetGetMethod(nonPublic: true);
        var resourceReferenceValue = modifiedValue_BaseValue_Getter.Invoke(rawEntry, new object[0]);

        // check the ResourceReference for the original ResourceKey
        var resourceReference = resourceReferenceValue.GetType();
        var resourceReference_resourceKey = resourceReference.GetField("_resourceKey", BindingFlags.NonPublic | BindingFlags.Instance);
        var resourceKey = resourceReference_resourceKey.GetValue(resourceReferenceValue);

        return resourceKey;
     }

     public static void SetDynamicResourceKey(this DependencyObject obj, DependencyProperty prop, object resourceKey)
     {
        var dynamicResource = new DynamicResourceExtension(resourceKey);
        var resourceReferenceExpression = dynamicResource.ProvideValue(null);
        obj.SetValue(prop, resourceReferenceExpression);
     }
  }

第二种方法使用DynamicResourceExtension 来避免对 Activator 造成一些麻烦,但第一种方法感觉非常脆弱。

我可以在我的原始示例中使用这些方法,如下所示:

  public MainWindow() {
     InitializeComponent();

     var key = TopBrush.GetDynamicResourceKey(SolidColorBrush.ColorProperty);
     BottomBrush.SetDynamicResourceKey(SolidColorBrush.ColorProperty, key);

     Resources["MyColor"] = Colors.Green;
  }

这适用于任何 DependencyProperty,只要在我们尝试获取资源键时将其设置为 DynamicResource。生产代码需要更多技巧。

【讨论】:

  • 我也不喜欢在我的代码中使用大量反射,但是您在标准程序集中进行了非常好的挖掘和探索工作!干得好!
  • 这在 .net 4.6 中对我来说就像一个冠军 - 它可能会在以后让我有些悲伤,但正是我需要它做的。谢谢。
【解决方案2】:

确实,您需要一个名为ResourceReferenceExpression 的内部对象。在 DynamicResourceExtention 中使用。

这是您可以使用的代码:

public MainWindow()
{
    InitializeComponent();

    BottomBrush.SetValue(SolidColorBrush.ColorProperty,
        Activator.CreateInstance(Type.GetType("System.Windows.ResourceReferenceExpression, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"), "MyColor"));

    Resources["MyColor"] = Colors.Green;
}

谨慎使用!由于ResourceReferenceExpression是内部的,也许会有原因(也许错误使用该对象会导致内存泄漏)。

【讨论】:

  • 几乎是我正在寻找的。有没有办法获得TopBrush 正在使用的ResourceReferenceExpression,然后对底部的表达式使用相同的表达式(或副本)?我宁愿不必为第二个画笔硬编码“MyColor” - 我希望它使用第一个画笔使用的任何内容。
  • 我想没有办法获得ResourceReferenceExpression:我们谈论的是内部对象,而不是公共对象。也许您应该改变解决问题的方法。也许您可以考虑将SolidColorBrush 作为资源(正如彼得建议的那样)。或者,也许您可​​以在 XAML 中而不是在代码中设置两种画笔颜色。
猜你喜欢
  • 1970-01-01
  • 2010-10-09
  • 1970-01-01
  • 2011-01-13
  • 1970-01-01
  • 2011-04-02
  • 1970-01-01
  • 2010-10-20
  • 1970-01-01
相关资源
最近更新 更多