在WPF中共享应用程序资源

4

此页面中,我读到:

如果您的应用程序使用自定义控件并在ResourceDictionary(或XAML资源节点)中定义资源,则建议您将资源定义为应用程序或窗口对象级别,或将其定义为自定义控件的默认主题。在自定义控件的ResourceDictionary中定义资源会对每个该控件实例造成性能影响。

好的...现在,我有一个UserControl定义了以下资源:

<UserControl ...>
    <UserControl.Resources>
        <Namespace:ImagesConverter x:Key="ImagesConverter" ...
        <Storyboard x:Key="AnimationHide" ...
    </UserControl.Resources>

因为我在运行时创建了不少于100个实例,如MSDN教程所说,最好将这些资源移动到MainWindow或App级别。应该将它们移动到哪个位置?MainWindow级别、App级别还是资源文件?为什么?
然后……我该怎么从它们的新位置使用它们?假设我在我的UserControl中有这段代码:
m_AnimationHide = (Storyboard)Resources["AnimationHide"];

我应该如何修改它以反映这些更改?我应该如何修改以下UserControl XAML片段呢?
Source="{Binding Source={x:Static Properties:Resources.MyImage}, Converter={StaticResource ImagesConverter}}"
2个回答

5
个人而言,我更喜欢使用App.xaml或单独的ResourceDictionary,而不是将它们全部添加到Window.Resources中,这样可以消除Window xaml中的混乱。
这也使您能够轻松地为应用程序创建Themes,因为所有内容都在一个地方,因此您可以复制现有的ResourceDictionary并更改画笔颜色等,然后选择要加载的ResourceDictionary并轻松更改整个应用程序的外观。
至于在Usercontrol中访问Resouces,在xaml方面没有区别,您将继续使用{StaticResource resourceKey},因为当您调用StaticResource时,它会搜索Resource层次结构以查找Resource
因此,如果您将资源从UserControl.Resources移动到Window.ResourcesApplication.Resources中,您不需要更改以通过{StaticResource resourceKey}访问的xaml代码中的任何内容。
至于在代码后台中的访问,则使用FindResource("resourceKey")而不是Resources["resourceKey"],因为FindResource会像xaml中的StaticResource一样在层次结构中搜索Resource
示例:
m_AnimationHide = (Storyboard)FindResource("AnimationHide");

如果您想在特定控件中修改这些资源,但它们已被冻结,您只需为该实例创建一个副本即可。
示例:
var animation = FindResource("AnimationHide") as Storyboard;

m_AnimationHide = animation.Clone();
m_AnimationHide.Completed += m_AnimationHide_Completed;

您还可以设置x:Shared="false",这将从资源中每次返回动画的新实例。如果您有一个需要在其中更改值的复杂资源,则可以节省复制/粘贴相同动画到应用程序中的时间。

<Storyboard x:Key="AnimationHide" x:Shared="false" />

接下来,您将能够在本地修改资源。


我按照你说的做了,但是出了些问题。我的UserControl中有一段代码看起来像这样:animation.Completed += handler;。每当我到达那里时,我的应用程序就会崩溃,并告诉我:“DoubleAnimation必须将IsFrozen设置为false才能进行修改”。这些动画必须在完成后对触发它们的UserControl执行某些操作。如何解决? - Tommaso Belluzzo
由于静态资源被冻结,您无法修改它,因此我已经在我的答案中添加了一个解决方法。 - sa_ddam213
谢谢!我尝试了第一种方法(全部放入App),它非常有效。现在我正在尝试第二种方法(全部放入MainWindow)......但是每当我尝试将MainWindow资源引用到我的自定义UserControl中时,我都会遇到未解决的资源错误。为什么? - Tommaso Belluzzo
因为不能保证UserControl将被放置在那个Window并且可以访问它的Resources,所以你应该使用{DynamicResource resourceKey}而不是{StaticResource resourceKey}。这样会在运行时解析。 - sa_ddam213
资源的层次结构为:本地对象 -> 可视树到根(托管控件的窗口)-> 应用程序资源。因此,如果您将控件放入主窗口中,则应该可以正常工作。如果控件托管在另一个窗口中,则无法找到资源。 - Roman Gruber

1
我想详细阐述一下将资源移出控制xaml的原因。
正如您在MSDN库中所读到的那样,将任何东西移动到更高的级别的唯一技术原因是为了节省资源(即内存)。 ResourceDictionary中的每个条目都会在运行时创建为对象(=实例!),占用内存,并且初始化需要几个CPU周期。
现在,像StoryBoard这样的东西就有点棘手了:存储与特定控件实例相关或甚至连接的数据的对象-例如执行动画的实例-必须为它应该驱动的每个控件实例创建。除非你希望所有控件都以相同的方式并行地进行动画。
对我来说,在代码中使用Clone而不是直接将其放入xaml中并不太合理。 Xaml方法需要一些复制/粘贴,因此如果您甚至可以跨控件重用此类内容,则意味着需要在多个位置修改动画,但它将消除代码要求并仍然执行相同操作:为每个控件实例创建一个Storyboard的实例。

使用Clone或者x:Shared(xaml版本的clone)是有道理的,因为如果你有一个“主题化”的应用程序,你如何在多个地方轻松地主题化复制/粘贴的资源呢。无论是使用xaml还是代码克隆,您只需要更改“Themed Dictionaries”中的资源,而不需要在整个应用程序中进行更改。 - sa_ddam213

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