如何在 WPF 的代码隐藏中复制资源引用?
How do I duplicate a resource reference in code behind in WPF?
在我的应用程序中,我有一个颜色资源。我在 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
您确实需要一个名为 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
是内部的,可能会有一个原因(可能错误使用该对象会导致内存泄漏)。
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。生产代码需要更多技巧。
在我的应用程序中,我有一个颜色资源。我在 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您确实需要一个名为 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
是内部的,可能会有一个原因(可能错误使用该对象会导致内存泄漏)。
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。生产代码需要更多技巧。