ContentPresenter窃取资源?

时间:2017-07-28 07:20:41

标签: c# wpf button styles contentpresenter

在一个简单的视图中,我想显示两个按钮,这些按钮具有内容图像,这些按钮是通过App.xaml中加载的额外style.xaml中的资源提供的。

<BitmapImage x:Key="AddIcon"  UriSource="pack://application:,,,/WpfTestBench;component/Images/plus.png"></BitmapImage>

<BitmapImage x:Key="RemoveIcon"  UriSource="pack://application:,,,/WpfTestBench;component/Images/minus.png"></BitmapImage>

<Style x:Key="AddButtonWithIconStyle" TargetType="{x:Type Button}">
  <Setter Property="Content">
    <Setter.Value>
      <Image Source="{DynamicResource AddIcon}" Stretch="None" 
           VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Setter.Value>
  </Setter>
</Style>

<Style x:Key="RemoveButtonWithIconStyle" TargetType="{x:Type Button}">
  <Setter Property="Content">
    <Setter.Value>
      <Image Source="{DynamicResource RemoveIcon}" Stretch="None" 
           VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Setter.Value>
  </Setter>
</Style>

在我看来,我使用这样的样式:

<DockPanel Margin="0,0,5,0" DockPanel.Dock="Bottom" LastChildFill="False" >
   <Button Command="{Binding DeleteCommand}" DockPanel.Dock="Right"  
              Style="{StaticResource RemoveButtonWithIconStyle}"/>
   <Button Command="{Binding AddCommand}" DockPanel.Dock="Right" Margin="5,0" 
              Style="{StaticResource AddButtonWithIconStyle}"/>
</DockPanel>

到目前为止这看起来不错。 但是当我通过ContentPresenter将另一个视图添加到此视图中时,它具有基本相同的内容(再次使用上述按钮样式),前两个按钮(父视图)的显示不再起作用。我得到的只有两个带有按钮功能的小圆圈。

<ContentPresenter Content="{Binding SomeEmbeddedViewModel, UpdateSourceTrigger=PropertyChanged}"/>

The upper list plus buttons is the part of the parent view, the second list and buttons are added with the content presenter.

为什么会这样? ContentPresenter是否会以某种方式阻止共享资源?

2 个答案:

答案 0 :(得分:2)

正如在另一个答案中所解释的那样,你应该在Button的ContentTemplate中拥有Image控件。

声明一个简单的图像按钮样式,其图像的Source属性绑定到实际内容:

<Style x:Key="ImageButtonStyle" TargetType="Button">
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <Image Source="{Binding}"/>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>
...
<Button Style="{StaticResource ImageButtonStyle}"
        Content="{DynamicResource RemoveIcon}" .../>

您也可以将样式声明为默认按钮样式,可能在DockPanel中:

<DockPanel>
    <DockPanel.Resources>
        <Style TargetType="Button">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <Image Source="{Binding}" Stretch="None"/>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </DockPanel.Resources>

    <Button Command="{Binding DeleteCommand}" DockPanel.Dock="Right"  
            Content="{DynamicResource RemoveIcon}"/>

    <Button Command="{Binding AddCommand}" DockPanel.Dock="Right" Margin="5,0" 
            Content="{DynamicResource AddIcon}"/>
</DockPanel>

答案 1 :(得分:1)

诊断

此行为的核心原因是元素只能在可视树中出现一次。

首先,ResourceDictionary中的资源默认是共享的,即每次获取具有特定键的资源时,您总是获得相同的实例。在您的情况下,您始终获得Style的相同实例,这也意味着始终只有Image的一个实例(针对每种风格)。

因此,如果将相同的样式应用于两个按钮,框架会尝试将相同的Image实例放在两个不同的位置,这是不允许的。为了避免这种情况,在尝试将图像加载到可视树中时,如果它已经在可视化树中,则首先从先前位置卸载它。

这就是为什么图像只能在最后一个按钮中显示(按照加载顺序)。

解决方案

这个问题基本上有两种解决方案。

<强> 1。禁用资源共享

您可以禁用资源共享,这样每次从ResourceDictionary获取资源时,您都会获得资源的新实例。为此,您需要在资源上设置x:Shared="False"

<Style x:Key="AddButtonWithIconStyle" x:Shared="False" TargetType="{x:Type Button}">
    (...)
</Style>

<强> 2。在您的风格中使用ContentTemplate

不是将图像作为按钮的内容放置,而是可以定义ContentTemplate,它是针对应用的每个按钮单独实现的(因此每个按钮都有自己的图像实例): / p>

<Style x:Key="RemoveButtonWithIconStyle" TargetType="{x:Type Button}">
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <Image Source="{DynamicResource RemoveIcon}" Stretch="None" 
                       VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

就我个人而言,我建议您使用第二种解决方案,因为这正是 WPF 中模板的用途。